Linear SDKを触ってみる
Linear TypeScript SDKはまだ開発中で使うのが非推奨であったが、とうとう正式?リリースされた。
触ってみる
ここでやっていく
インスコ
npm install @linear/sdk
APIを叩いているユーザを取得
import { LinearClient } from "@linear/sdk";
(async ()=>{
const apiKey = process.env.LINEAR_API_KEY
const linearClient = new LinearClient({ apiKey });
const currentUser = await linearClient.viewer
console.log(currentUser)
})()
❯ npx ts-node index.ts
cm {
_request: [Function (anonymous)],
active: true,
admin: true,
archivedAt: undefined,
avatarUrl: undefined,
createdAt: 2021-01-29T04:11:10.108Z,
createdIssueCount: 8,
displayName: 'futa-hirakoba',
email: 'futa-hirakoba@cybozu.co.jp',
id: '80e102b0-xxxx-xxxx-xxxx-044bcfb4cd39',
inviteHash: 'xxxxxxxxxxxxxxx',
lastSeen: undefined,
name: 'futa-hirakoba@cybozu.co.jp',
updatedAt: 2021-03-06T07:04:21.905Z
}
cmってなんだ?
viewer
で取ってこれるUserクラスはassignedIssues()
を持っている。
これを叩くと自分がアサインされたIssue一覧を取得できる
async function getMyIssues() {
const me = await linearClient.viewer;
const myIssues = await me.assignedIssues();
console.log(myIssues);
}
(async () => {
await getMyIssues();
})();
❯ npx ts-node index.ts
Xs {
_request: [Function (anonymous)],
_fetch: [Function (anonymous)],
nodes: [
Ys {
_request: [Function (anonymous)],
archivedAt: undefined,
autoArchivedAt: undefined,
autoClosedAt: undefined,
boardOrder: 958.81,
branchName: 'futa-hirakoba/kor-14-kkkk',
canceledAt: 2021-01-30T14:50:00.249Z,
completedAt: undefined,
createdAt: 2021-01-30T13:39:19.901Z,
description: '### 受け入れするよ\n\n* aaa\n* ddd\n\n#### チェックリスト\n\n* [ ] aaaaaa\n* [ ] fffff',
dueDate: undefined,
estimate: 2,
id: '17d993f0-82a1-4a43-850a-0928ac805316',
identifier: 'KOR-14',
number: 14,
previousIdentifiers: [],
priority: 1,
priorityLabel: 'Urgent',
startedAt: undefined,
subIssueSortOrder: -4,
title: 'kkkk',
updatedAt: 2021-01-30T14:50:00.231Z,
url: 'https://linear.app/korosuke613/issue/KOR-14/kkkk',
_assignee: [Object],
_creator: [Object],
_cycle: [Object],
_parent: undefined,
_project: [Object],
_state: [Object],
_team: [Object]
},
Ys { ... },
Ys { ... }
],
pageInfo: Ou {
_request: [Function (anonymous)],
endCursor: '236e0fe8-ff57-4066-9756-b2df06e33810',
hasNextPage: false,
hasPreviousPage: false,
startCursor: '17d993f0-82a1-4a43-850a-0928ac805316'
}
}
Xs, Ysってなんだ?
どうやらIssueConnection
クラスが手に入るらしい。
IssueConnection
はnode
を持っていて、これがIssueの配列となるようだ
IssueクラスはGraghQLで定義されており、TSのコードは自動生成されたものらしい。 https://github.com/linear/linear/blob/c2f6c67af0fce5cfc26ead6ff839571a60866957/packages/sdk/src/schema.graphql#L1597
おそらく自動生成されたクラス↓。リポジトリに存在しない
/**
* An issue.
*
* @param request - function to call the graphql client
* @param data - L.IssueFragment response data
*/
export declare class Issue extends Request {
private _assignee?;
private _creator?;
private _cycle?;
private _parent?;
private _project?;
private _state?;
private _team?;
constructor(request: LinearRequest, data: L.IssueFragment);
/** The time at which the entity was archived. Null if the entity has not been archived. */
archivedAt?: Date;
/** The time at which the issue was automatically archived by the auto pruning process. */
autoArchivedAt?: Date;
/** The time at which the issue was automatically closed by the auto pruning process. */
autoClosedAt?: Date;
/** The order of the item in its column on the board. */
boardOrder?: number;
/** Suggested branch name for the issue. */
branchName?: string;
/** The time at which the issue was moved into canceled state. */
canceledAt?: Date;
/** The time at which the issue was moved into completed state. */
completedAt?: Date;
/** The time at which the entity was created. */
createdAt?: Date;
/** The issue's description in markdown format. */
description?: string;
/** The date at which the issue is due. */
dueDate?: Date;
/** The estimate of the complexity of the issue.. */
estimate?: number;
/** The unique identifier of the entity. */
id?: string;
/** Issue's human readable identifier (e.g. ENG-123). */
identifier?: string;
/** The issue's unique number. */
number?: number;
/** Previous identifiers of the issue if it has been moved between teams. */
previousIdentifiers?: string[];
/** The priority of the issue. */
priority?: number;
/** Label for the priority. */
priorityLabel?: string;
/** The time at which the issue was moved into started state. */
startedAt?: Date;
/** The order of the item in the sub-issue list. Only set if the issue has a parent. */
subIssueSortOrder?: number;
/** The issue's title. */
title?: string;
/**
* The last time at which the entity was updated. This is the same as the creation time if the
* entity hasn't been update after creation.
*/
updatedAt?: Date;
/** Issue URL. */
url?: string;
/** The user to whom the issue is assigned to. */
get assignee(): LinearFetch<User> | undefined;
/** The user who created the issue. */
get creator(): LinearFetch<User> | undefined;
/** The cycle that the issue is associated with. */
get cycle(): LinearFetch<Cycle> | undefined;
/** The parent of the issue. */
get parent(): LinearFetch<Issue> | undefined;
/** The project that the issue is associated with. */
get project(): LinearFetch<Project> | undefined;
/** The workflow state that the issue is associated with. */
get state(): LinearFetch<WorkflowState> | undefined;
/** The team that the issue is associated with. */
get team(): LinearFetch<Team> | undefined;
/** Attachments associated with the issue. */
attachments(variables?: Omit<L.Issue_AttachmentsQueryVariables, "id">): LinearFetch<AttachmentConnection> | undefined;
/** Children of the issue. */
children(variables?: Omit<L.Issue_ChildrenQueryVariables, "id">): LinearFetch<IssueConnection> | undefined;
/** Comments associated with the issue. */
comments(variables?: Omit<L.Issue_CommentsQueryVariables, "id">): LinearFetch<CommentConnection> | undefined;
/** History entries associated with the issue. */
history(variables?: Omit<L.Issue_HistoryQueryVariables, "id">): LinearFetch<IssueHistoryConnection> | undefined;
/** Inverse relations associated with this issue. */
inverseRelations(variables?: Omit<L.Issue_InverseRelationsQueryVariables, "id">): LinearFetch<IssueRelationConnection> | undefined;
/** Labels associated with this issue. */
labels(variables?: Omit<L.Issue_LabelsQueryVariables, "id">): LinearFetch<IssueLabelConnection> | undefined;
/** Relations associated with this issue. */
relations(variables?: Omit<L.Issue_RelationsQueryVariables, "id">): LinearFetch<IssueRelationConnection> | undefined;
/** Users who are subscribed to the issue. */
subscribers(variables?: Omit<L.Issue_SubscribersQueryVariables, "id">): LinearFetch<UserConnection> | undefined;
/** Archives an issue. */
archive(): LinearFetch<ArchivePayload> | undefined;
/** Unarchives an issue. */
unarchive(): LinearFetch<ArchivePayload> | undefined;
/** Updates an issue. */
update(input: L.IssueUpdateInput): LinearFetch<IssuePayload> | undefined;
}
型としての Issue は@linear/sdk/dist/_generated_documents
にあった。
import { Issue } from "@linear/sdk/dist/_generated_documents";
const issue: Issue = {
attachments: undefined,
boardOrder: 0,
branchName: "",
children: undefined,
comments: undefined,
createdAt: undefined,
history: undefined,
identifier: "",
integrationResources: undefined,
inverseRelations: undefined,
number: 0,
previousIdentifiers: undefined,
priorityLabel: "",
relations: undefined,
state: undefined,
subscribers: undefined,
team: undefined,
title: "",
updatedAt: undefined,
url: "",
labels: undefined,
startedAt: undefined,
id: "hoge",
priority: 2,
};
クラスとしての Issue を@linear/sdk
から取ってこれるが、こちらはrequestとdataを引数に入れる必要がある。絶対めんどい
request
export declare type LinearRequest = <LinearResponse, Variables extends Record<string, unknown>>(doc: DocumentNode, variables?: Variables) => Promise<LinearResponse>;
data
export declare type IssueFragment = {
__typename?: "Issue";
} & Pick<Issue, "url" | "identifier" | "priorityLabel" | "previousIdentifiers" | "branchName" | "dueDate" | "estimate" | "description" | "title" | "number" | "updatedAt" | "boardOrder" | "subIssueSortOrder" | "priority" | "archivedAt" | "createdAt" | "autoArchivedAt" | "autoClosedAt" | "canceledAt" | "completedAt" | "startedAt" | "id"> & {
cycle?: Maybe<{
__typename?: "Cycle";
} & Pick<Cycle, "id">>;
parent?: Maybe<{
__typename?: "Issue";
} & Pick<Issue, "id">>;
project?: Maybe<{
__typename?: "Project";
} & Pick<Project, "id">>;
team: {
__typename?: "Team";
} & Pick<Team, "id">;
assignee?: Maybe<{
__typename?: "User";
} & Pick<User, "id">>;
creator?: Maybe<{
__typename?: "User";
} & Pick<User, "id">>;
state: {
__typename?: "WorkflowState";
} & Pick<WorkflowState, "id">;
};
Issue型とIssueFragment型の違いは?
cycleやparentなどのオブジェクトを含んでいることか?
webhookのdataを突っ込むならこっちの方がいい?
webhookで送られてくるdata
にteamId
が含まれているんだが、Issue型には含まれないようだ
IssueCreateInput型ならと思ったけどやはりダメそう
Webhookを登録するという意味でのWebhook型はあるが、送られてくるWebhookに関する型は残念ながら無いらしい。終わり!また調査することがあったらオープンする