@@ -2,32 +2,60 @@ import { graphql } from "@octokit/graphql";
22
33import { ILogger , IProjectApi , Issue , Repository } from "./types" ;
44
5- interface ProjectData {
6- organization : {
7- projectNext : {
8- id : string ;
9- fields : {
10- nodes : {
11- id : string ;
12- name : string ;
13- settings ?: string | null ;
14- } [ ] ;
15- } ;
16- } ;
17- } ;
5+ type NodeData = { id : string ; title : string } ;
6+
7+ export const PROJECT_V2_QUERY : string = `
8+ query($organization: String!, $number: Int!) {
9+ organization(login: $organization){
10+ projectV2(number: $number) {
11+ id
12+ title
13+ }
14+ }
1815}
16+ ` ;
1917
20- interface CreatedProjectItemForIssue {
21- addProjectNextItem : { projectNextItem : { id : string } } ;
18+ export const ADD_PROJECT_V2_ITEM_BY_ID_QUERY : string = `
19+ mutation($project: ID!, $issue: ID!) {
20+ addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
21+ item {
22+ id
23+ }
24+ }
25+ }
26+ ` ;
27+
28+ export const UPDATE_PROJECT_V2_ITEM_FIELD_VALUE_QUERY : string = `
29+ mutation (
30+ $project: ID!
31+ $item: ID!
32+ $targetField: ID!
33+ $targetFieldValue: String!
34+ ) {
35+ updateProjectV2ItemFieldValue(
36+ input: {
37+ projectId: $project
38+ itemId: $item
39+ fieldId: $targetField
40+ value: {
41+ singleSelectOptionId: $targetFieldValue
42+ }
43+ }
44+ ) {
45+ projectV2Item {
46+ id
47+ }
48+ }
2249}
50+ ` ;
2351
2452/**
2553 * Instance that manages the GitHub's project api
2654 * ? Octokit.js doesn't support Project v2 API yet so we need to use graphQL
2755 * Used this blog post as a reference for the queries: https://www.cloudwithchris.com/blog/automate-adding-gh-issues-projects-beta/
2856 */
2957export class ProjectKit implements IProjectApi {
30- private projectNodeId : string | null = null ;
58+ private projectNode : NodeData | null = null ;
3159
3260 /** Requires an instance with a PAT with the 'write:org' permission enabled */
3361 constructor (
@@ -37,122 +65,83 @@ export class ProjectKit implements IProjectApi {
3765 private readonly logger : ILogger ,
3866 ) { }
3967
40- /* changeIssueStateInProject(issueCardId: number, state: "todo" | "in progress" | "blocked" | "done"): Promise<void> {
41- return this.gql(
42- `
43- mutation (
44- $project: ID!
45- $item: ID!
46- $targetField: ID!
47- $targetFieldValue: String!
48- ) {
49- updateProjectNextItemField(input: {
50- projectId: $project
51- itemId: $item
52- fieldId: $targetField
53- value: $targetFieldValue
54- }) {
55- projectNextItem {
56- id
57- }
58- }
59- }
60- `,
61- { project: this.projectNumber, item: issueCardId, targetField: "Status", targetFieldValue: state },
62- );
63- } */
68+ /*
69+ changeIssueStateInProject(issueCardId: number, state: "todo" | "in progress" | "blocked" | "done"): Promise<void> {
70+ return this.gql(UPDATE_STATE_IN_PROJECT_QUERY, {
71+ project: this.projectNumber,
72+ item: issueCardId,
73+ targetField: "Status",
74+ targetFieldValue: state,
75+ });
76+ } */
6477
6578 /**
6679 * Fetches the node id from the project id and caches it.
6780 * @returns node_id of the project. This value never changes so caching it per instance is effective
6881 */
69- async fetchProjectId ( ) : Promise < string > {
70- if ( this . projectNodeId ) {
71- return this . projectNodeId ;
82+ async fetchProjectData ( ) : Promise < NodeData > {
83+ if ( this . projectNode ) {
84+ return this . projectNode ;
7285 }
7386
74- // Source: https://docs.github.com/en/graphql/reference/objects#projectnext
75- const projectData = await this . gql < ProjectData > (
76- `
77- query($organization: String!, $number: Int!) {
78- organization(login: $organization){
79- projectNext(number: $number) {
80- id
81- fields(first: 20) {
82- nodes {
83- id
84- name
85- settings
86- }
87- }
88- }
89- }
90- }
91- ` ,
92- { organization : this . repoData . owner , number : this . projectNumber } ,
93- ) ;
87+ try {
88+ // Source: https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/using-the-api-to-manage-projects#using-variables
89+ const projectData = await this . gql < { organization : { projectV2 : NodeData } } > ( PROJECT_V2_QUERY , {
90+ organization : this . repoData . owner ,
91+ number : this . projectNumber ,
92+ } ) ;
9493
95- this . projectNodeId = projectData . organization . projectNext . id ;
94+ this . projectNode = projectData . organization . projectV2 ;
9695
97- return projectData . organization . projectNext . id ;
96+ return projectData . organization . projectV2 ;
97+ } catch ( e ) {
98+ this . logger . error ( "Failed while executing the 'PROJECT_V2_QUERY' query" ) ;
99+ throw e ;
100+ }
98101 }
99102
100- // step three
101103 async updateProjectNextItemField (
102104 project : string ,
103105 item : string ,
104106 targetField : string ,
105107 targetFieldValue : string ,
106108 ) : Promise < void > {
107- await this . gql (
108- `
109- mutation (
110- $project: ID!
111- $item: ID!
112- $targetField: ID!
113- $targetFieldValue: String!
114- ) {
115- updateProjectNextItemField(input: {
116- projectId: $project
117- itemId: $item
118- fieldId: $targetField
119- value: $targetFieldValue
120- }) {
121- projectNextItem {
122- id
123- }
124- }
125- }
126- ` ,
127- { project, item, targetField, targetFieldValue } ,
128- ) ;
109+ await this . gql ( UPDATE_PROJECT_V2_ITEM_FIELD_VALUE_QUERY , { project, item, targetField, targetFieldValue } ) ;
129110 }
130111
131112 async assignIssueToProject ( issue : Issue , projectId : string ) : Promise < boolean > {
132- const migration = await this . gql < CreatedProjectItemForIssue > (
133- `
134- mutation($project: ID!, $issue: ID!) {
135- addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
136- projectNextItem {
137- id
138- }
139- }
140- }
141- ` ,
142- { project : projectId , issue : issue . node_id } ,
143- ) ;
144-
145- // TODO: Check what is this ID
146- return ! ! migration . addProjectNextItem . projectNextItem . id ;
113+ try {
114+ const migration = await this . gql < { addProjectV2ItemById : { item : { id : string } } } > (
115+ ADD_PROJECT_V2_ITEM_BY_ID_QUERY ,
116+ { project : projectId , issue : issue . node_id } ,
117+ ) ;
118+
119+ return ! ! migration . addProjectV2ItemById . item . id ;
120+ } catch ( e ) {
121+ this . logger . error ( "Failed while executing 'ADD_PROJECT_V2_ITEM_BY_ID_QUERY' query" ) ;
122+ throw e ;
123+ }
147124 }
148125
149- async assignIssue ( issue : Issue ) : Promise < boolean > {
150- const projectId = await this . fetchProjectId ( ) ;
126+ async addIssueToProject ( issue : Issue , project : NodeData ) : Promise < boolean > {
127+ this . logger . info ( `Syncing issue # ${ issue . number } for ${ project . title } ` ) ;
151128
152- this . logger . info ( `Syncing issue #${ issue . number } for ${ this . projectNumber } ` ) ;
129+ return await this . assignIssueToProject ( issue , project . id ) ;
130+ }
153131
154- return await this . assignIssueToProject ( issue , projectId ) ;
132+ async assignIssue ( issue : Issue ) : Promise < boolean > {
133+ const project = await this . fetchProjectData ( ) ;
134+
135+ return await this . addIssueToProject ( issue , project ) ;
155136
156137 // TODO: Assign targetField
157138 }
139+
140+ async assignIssues ( issues : Issue [ ] ) : Promise < boolean [ ] > {
141+ const project = await this . fetchProjectData ( ) ;
142+
143+ const issueAssigment = issues . map ( ( issue ) => this . addIssueToProject ( issue , project ) ) ;
144+
145+ return await Promise . all ( issueAssigment ) ;
146+ }
158147}
0 commit comments