Closed10

Linear SDKを触ってみる

Futa HirakobaFuta Hirakoba

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ってなんだ?

Futa HirakobaFuta Hirakoba

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クラスが手に入るらしい。
IssueConnectionnodeを持っていて、これがIssueの配列となるようだ

Futa HirakobaFuta Hirakoba

IssueクラスはGraghQLで定義されており、TSのコードは自動生成されたものらしい。 https://github.com/linear/linear/blob/c2f6c67af0fce5cfc26ead6ff839571a60866957/packages/sdk/src/schema.graphql#L1597

おそらく自動生成されたクラス↓。リポジトリに存在しない

node_modules/@linear/sdk/dist/_generated_sdk.d.ts
/**
 * 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;
}
Futa HirakobaFuta Hirakoba

型としての 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,
};
Futa HirakobaFuta Hirakoba

クラスとしての Issue を@linear/sdkから取ってこれるが、こちらはrequestとdataを引数に入れる必要がある。絶対めんどい

request

node_modules/@linear/sdk/dist/_generated_sdk.d.ts
export declare type LinearRequest = <LinearResponse, Variables extends Record<string, unknown>>(doc: DocumentNode, variables?: Variables) => Promise<LinearResponse>;

data

node_modules/@linear/sdk/dist/_generated_documents.d.ts
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">;
};
Futa HirakobaFuta Hirakoba

Issue型とIssueFragment型の違いは?
cycleやparentなどのオブジェクトを含んでいることか?

webhookのdataを突っ込むならこっちの方がいい?

Futa HirakobaFuta Hirakoba

webhookで送られてくるdatateamIdが含まれているんだが、Issue型には含まれないようだ
IssueCreateInput型ならと思ったけどやはりダメそう

Futa HirakobaFuta Hirakoba

Webhookを登録するという意味でのWebhook型はあるが、送られてくるWebhookに関する型は残念ながら無いらしい。終わり!また調査することがあったらオープンする

このスクラップは2021/03/06にクローズされました