I tried converting external data into Tickets using DevRev snap-in

I tried converting external data into Tickets using DevRev snap-in

Using DevRev snap-ins, you can customize mechanisms to integrate with external APIs. As an example, we'll introduce the procedure to load BigQuery's public samples and convert them into Tickets.
2026.01.25

This page has been translated by machine translation. View original

Introduction

When considering introducing DevRev, how it can connect with existing systems becomes a decision factor. It's important to be able to flow information to the DevRev side without significantly changing existing workflows.

DevRev provides a mechanism through snap-ins (extensions) to call external system APIs and create objects on the DevRev side. In this article, we'll create one snap-in using BigQuery public samples as an example of external data. We'll then execute our custom command and confirm the creation of a DevRev Ticket.

Testing Environment

  • Windows 11
  • Git Bash 2.47.1.windows.1
  • DevRev CLI v0.4.13
  • node v24.13.0
  • npm 11.6.2

Target Audience

  • Those considering DevRev implementation who want to quickly verify the feasibility of external integration via snap-ins
  • Those who already have a DevRev dev org with Admin privileges
  • Those comfortable with Node.js and TypeScript basics in a Windows + Git Bash environment

References

Creating a snap-in

Prerequisites

  • A DevRev dev org with Admin privileges (for draft installation of the snap-in)
  • A GCP service account JSON file ready to be placed locally as service-account.json
  • A Part already created with at least one owner (owned_by) assigned

Preparing the DevRev CLI

To create a snap-in, use the DevRev CLI. On Windows, extract the DevRev CLI zip and add the path to devrev.exe to your PATH.

devrev --version

Example output:

devrev version v0.4.13 fe3410c6efb59f249e3cc29ba113d382357e15ce 2025-11-10T17:15:52Z
gateway version d7a6c473fc2e1c1827f6bcbff51f33ecd57cfc02

Logging into DevRev

Log into your DevRev workspace with the following command. A browser will open for authentication.

devrev profiles authenticate -o <dev-org-slug> -u <your-email@example.com>

Initializing the TypeScript Template

Generate a snap-in TypeScript template.

devrev snap_in_version init

After execution, a devrev-snaps-typescript-template directory will be created.

Creating a snap-in package

Create a snap-in package. Make the slug globally unique.

devrev snap_in_package create-one --slug <globally-unique-slug>

Updating manifest.yaml

For this test, it's sufficient if we can call a function from a Discussions command. Define functions and commands in the manifest. Set the command's surfaces to discussions and object_types to part.

The namespace is used to distinguish commands with the same name. You don't need to input the namespace during normal execution.

Update devrev-snaps-typescript-template/manifest.yaml as follows. The command name is arbitrary. In this article, we'll use bqimport.

version: "2"

name: "External Ticket Import Demo"
description: "Demo: run a command and create tickets from an external system."

service_account:
  display_name: "External Import Bot"

developer_keyrings:
  - name: gcp_service_account_json
    description: "GCP service account JSON for external query"
    display_name: "GCP Service Account JSON"

functions:
  - name: bq_import
    description: "Command handler: query external system and create tickets"

commands:
  - name: bqimport
    namespace: demo
    description: "Create tickets from an external system."
    surfaces:
      - surface: discussions
        object_types:
          - part
    usage_hint: "[limit] (e.g. 10)"
    function: bq_import

developer_keyrings are secret information provided by developers. They are not visible to the snap-in installer.

Creating a developer keyring

In DevRev, secret information can be stored as keyrings. A developer keyring's snap-in secret can store plain text. Using the CLI, register your authentication information (e.g., service-account.json) with the devrev developer_keyring command.

cat service-account.json | devrev developer_keyring create snap-in-secret gcp_service_account_json

Adding a function

In the template, functions are registered in function-factory.ts. Add bq_import.

Update devrev-snaps-typescript-template/code/src/function-factory.ts:

import on_work_creation from './functions/on_work_creation';
import bq_import from './functions/bq_import';

export const functionFactory = {
  // Add your functions here
  on_work_creation,
  bq_import,
} as const;

export type FunctionFactoryType = keyof typeof functionFactory;

Next, create devrev-snaps-typescript-template/code/src/functions/bq_import/index.ts:

  • Get the service account JSON from the keyring (in the example, refer to event.input_data.keyrings.*)
  • Initialize the DevRev SDK with service_account_token
  • Get the command's execution source from event.payload.source_id
  • Create a Ticket with works.create, always specifying applies_to_part and owned_by
  • For owned_by, use the Part's owner retrieved with parts.get
devrev-snaps-typescript-template/code/src/functions/bq_import/index.ts full text
import { BigQuery } from "@google-cloud/bigquery";
import { client, publicSDK } from "@devrev/typescript-sdk";

const DEFAULT_LIMIT = 10;
const MAX_LIMIT = 200;

const SQL = (limit: number) => `
SELECT word, SUM(word_count) AS cnt
FROM \`bigquery-public-data.samples.shakespeare\`
GROUP BY word
ORDER BY cnt DESC
LIMIT ${limit};
`;

function parseLimit(event: any): number {
  const raw =
    event?.payload?.parameters ??
    event?.payload?.params ??
    event?.payload?.command_params ??
    "";

  const n = Number(String(raw).trim().split(/\s+/)[0] || DEFAULT_LIMIT);
  if (!Number.isFinite(n)) return DEFAULT_LIMIT;
  return Math.max(1, Math.min(MAX_LIMIT, Math.floor(n)));
}

async function getPartOwnerIds(partId: string, devrevSDK: any): Promise<string[]> {
  const resp = await devrevSDK.partsGet({ id: partId });
  const owned = resp?.data?.part?.owned_by ?? [];
  return owned.map((u: any) => u?.id).filter(Boolean);
}

export const run = async (events: any[]) => {
  // TS7030 workaround: always return even if events is empty
  if (!events || events.length === 0) {
    return { created: 0, reason: "no events" };
  }

  // Commands are usually single, so process just one
  const event = events[0];

  console.info("event.payload keys:", Object.keys(event?.payload ?? {}));

  const endpoint = event.execution_metadata.devrev_endpoint;
  const token = event.context.secrets.service_account_token;
  const devrevSDK = client.setup({ endpoint, token });

  // developer keyring
  const saJson = event.input_data.keyrings.gcp_service_account_json;
  if (!saJson) {
    throw new Error("Missing developer keyring: gcp_service_account_json");
  }

  const creds = JSON.parse(saJson);
  const bigquery = new BigQuery({
    projectId: creds.project_id,
    credentials: {
      client_email: creds.client_email,
      private_key: creds.private_key,
    },
  });

  const limit = parseLimit(event);

  const [rows] = await bigquery.query({
    query: SQL(limit),
    location: "US",
    // TS2769 workaround: maximumBytesBilled is string
    maximumBytesBilled: String(100 * 1024 * 1024),
    useLegacySql: false,
  });

  const partId = event.payload.source_id;
  if (!partId) {
    throw new Error("Missing payload.source_id (expected Part id).");
  }

  const ownedBy = await getPartOwnerIds(partId, devrevSDK);
  if (ownedBy.length === 0) {
    throw new Error("Part has no owners (owned_by). Please set an owner on the Part.");
  }

  let created = 0;
  for (const r of rows as any[]) {
    const word = String(r.word);
    const cnt = Number(r.cnt);

    await devrevSDK.worksCreate({
      type: publicSDK.WorkType.Ticket,
      title: `[BQ] shakespeare: ${word}`,
      body: `count=${cnt}`,
      applies_to_part: partId,
      owned_by: ownedBy,
    });
    created += 1;
  }

  return { created, part: partId, limit };
};

export default run;

Installing dependencies and building

Node.js is used to build and package the template.

npm install
npm install @google-cloud/bigquery
npm run build
npm run package

Creating a snap-in version and installing it as a draft

Return to the template root.

cd /path/to/devrev-snaps-typescript-template

Create a snap-in version.

devrev snap_in_version create-one --path .

Because the manifest contains developer_keyrings, the CLI will request keyring mapping. Here, select the developer keyring with the same name for gcp_service_account_json.

Then, install the snap-in as a draft. Admin privileges are required for draft installation.

devrev snap_in draft

Confirm that the snap-in is active in the DevRev UI.

activated snap-in

Testing

Since we specified surface: discussions and object_types: part in the manifest, we can execute the command in the Part's Discussions.

product page

Let's try running this command:

/bqimport 10

After execution, 10 Tickets linked to the Part were created.

created tickets

Snap-in logs can be checked using the CLI.

devrev snap_in_package logs

Example output:

{
    "incomplete_results": false,
    "log_summary": {
        "total": {
            "relation": "eq",
            "value": 3
        }
    },
    "logs": [
        {
            "id": "****",
            "level": "info",
            "msg": "END RequestId: **** Execution time: 5255.87 ms",
            "process": "function",
            "snap_in_package": "don:integration:dvrv-us-1:devo/****:snap_in_package/****",
            "timestamp": "2026-01-25T08:48:01.45Z"
        },
        {
            "dev_org": "don:identity:dvrv-us-1:devo/****",
            "id": "****",
            "level": "info",
            "msg": "event.payload keys: [\n  'actor_id',\n  'command_id',\n  'dev_org',\n  'parameters',\n  'parent_id',\n  'request_id',\n  'source_id',\n  'surface'\n]",
            "process": "function",
            "snap_in_package": "don:integration:dvrv-us-1:devo/****:snap_in_package/****",
            "snap_in_version": "don:integration:dvrv-us-1:devo/****:snap_in_package/****:snap_in_version/****",
            "source_type": "command",
            "timestamp": "2026-01-25T08:47:58.091Z"
        },
        {
            "id": "****",
            "level": "info",
            "msg": "START RequestId: ****",
            "process": "function",
            "snap_in_package": "don:integration:dvrv-us-1:devo/****:snap_in_package/****",
            "timestamp": "2026-01-25T08:47:56.194Z"
        }
    ],
    "next_cursor": "****",
    "prev_cursor": "****"
}

Summary

Using snap-ins, you can trigger DevRev UI operations (Discussions commands), access external systems, and create Tickets on the DevRev side. While this article used BigQuery as an example, the structure of passing secret information through keyrings and calling external APIs and works.create within the function is common. I hope this provides useful material for evaluating the feasibility of existing system integration when considering DevRev implementation.

Share this article

FacebookHatena blogX

Related articles