
DevRev snap-inで外部データをTicket化してみた
はじめに
DevRev を導入検討するとき、既存システムとどう繋げられるかが判断材料になります。既存のワークフローを大きく変えずに DevRev 側へ情報を流せるかが重要です。
DevRev は snap-in (拡張機能) によって、外部システムの API を呼び出し、DevRev 側のオブジェクトを作成する仕組みを提供します。本記事では、外部データの例として BigQuery の公開サンプルを使い、snap-in を 1 本作ります。その後自前のコマンドを実行し、DevRev の Ticket が作成されるところまでを確認します。
検証環境
- Windows 11
- Git Bash 2.47.1.windows.1
- DevRev CLI v0.4.13
- node v24.13.0
- npm 11.6.2
対象読者
- DevRev の導入を検討しており、snap-in による外部連携の実現性を短時間で確認したい方
- DevRev の dev org を用意済みで、Admin 権限を持つ方
- Windows + Git Bash の環境で、Node.js と TypeScript の基本操作に慣れている方
参考
- DevRev CLI
- Getting started (snap-in development)
- Snap-in manifest
- Developer keyrings (developer keyring / snap-in secret)
- Commands
- Works: Create
snap-in を作成する
前提
- DevRev の dev org を用意済みで、Admin 権限を持つこと (snap-in を draft インストールするため)
- GCP のサービスアカウント JSON は用意済みで、
service-account.jsonとしてローカルに置けること - Ticket を紐付ける Part は作成済みで、owner (owned_by) が 1 人以上設定されていること
DevRev CLI の準備
snap-in を作成するには DevRev CLI を使います。Windows では、DevRev CLI の zip を展開し、devrev.exe へのパスを PATH に追加します。
devrev --version
実行結果の例
devrev version v0.4.13 fe3410c6efb59f249e3cc29ba113d382357e15ce 2025-11-10T17:15:52Z
gateway version d7a6c473fc2e1c1827f6bcbff51f33ecd57cfc02
DevRev にログインする
下記のコマンドで DevRev ワークスペースにログインします。ブラウザが開くので認証します。
devrev profiles authenticate -o <dev-org-slug> -u <your-email@example.com>
TypeScript テンプレートを初期化する
snap-in の TypeScript テンプレートを生成します。
devrev snap_in_version init
実行後、 devrev-snaps-typescript-template ディレクトリが作られます。
snap-in package を作成する
snap-in package を作ります。slug はグローバルで一意になるようにします。
devrev snap_in_package create-one --slug <globally-unique-slug>
manifest.yaml を更新する
この検証では、Discussions の command から function を呼び出せれば十分です。manifest には functions と commands を定義します。command の surfaces を discussions にし、object_types を part にします。
namespace は、同名 command がある場合の区別に使います。普段の実行で namespace を入力する必要はありません。
devrev-snaps-typescript-template/manifest.yaml を次のようにします。 command 名は任意です。本記事では 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 は、開発者が提供する秘密情報です。snap-in のインストーラには見えません。
developer keyring を作成する
DevRev では、秘密情報を keyring として保持できます。developer keyring の snap-in secret は plain text を格納できます。CLI では、devrev developer_keyring コマンドで認証情報 (例: service-account.json) を登録します。
cat service-account.json | devrev developer_keyring create snap-in-secret gcp_service_account_json
function を追加する
テンプレートでは function-factory.ts に function を登録します。bq_import を追加します。
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;
次に、devrev-snaps-typescript-template/code/src/functions/bq_import/index.ts を作成します。
- keyring からサービスアカウント JSON を取得 (例では
event.input_data.keyrings.*を参照) - DevRev SDK は
service_account_tokenで初期化 - command の実行元は
event.payload.source_idから取得 - Ticket は
works.createで作成し、applies_to_partとowned_byを必ず指定 owned_byには Part の owner をparts.getで取得して使用
devrev-snaps-typescript-template/code/src/functions/bq_import/index.ts 全文
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 対策: events が空でも必ず return
if (!events || events.length === 0) {
return { created: 0, reason: "no events" };
}
// コマンドは通常 1 件なので 1 件だけ処理
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 対策: maximumBytesBilled は 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;
依存関係をインストールしてビルドする
テンプレートのビルドとパッケージングには Node.js を使います。
npm install
npm install @google-cloud/bigquery
npm run build
npm run package
snap-in version を作成し、draft をインストールする
テンプレートのルートに戻ります。
cd /path/to/devrev-snaps-typescript-template
snap-in version を作成します。
devrev snap_in_version create-one --path .
manifest に developer_keyrings があるため、CLI は keyring のマッピングを要求します。ここでは gcp_service_account_json に対して、先ほど作った同名の developer keyring を選びます。
続けて、snap-in を draft としてインストールします。draft インストールには Admin 権限が必要です。
devrev snap_in draft
DevRev の UI 上で snap-in が active になることを確認します。

動作確認
manifest で surface: discussions と object_types: part を指定したため、Part の Discussions で command を実行できるようになっています。

試しに次のコマンドを実行してみました。
/bqimport 10
実行後、Part に紐づく Ticket が 10 件作成されました。

snap-in のログは CLI で確認できます。
devrev snap_in_package logs
実行結果の例
{
"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": "****"
}
まとめ
snap-in を使うと、DevRev の UI 操作 (Discussions の command) をトリガーにし、外部システムへアクセスし、DevRev 側に Ticket を作成できます。本記事の手順は BigQuery を例にしましたが、keyring で秘密情報を渡し、function 内で外部 API と works.create を呼ぶ構造は共通です。DevRev 導入検討において、既存システム連携の実現可能性を確認する材料になれば幸いです。







