11import { graphql } from "@octokit/graphql" ;
22
3- import { ILogger , IProjectApi , Issue , Repository } from "./types" ;
3+ import { FieldValues , ILogger , IProjectApi , Issue , Repository } from "./types" ;
44
55type NodeData = { id : string ; title : string } ;
6+ type FieldData = { name : string ; id : string ; options ?: { name : string ; id : string } [ ] } ;
67
78export const PROJECT_V2_QUERY : string = `
89query($organization: String!, $number: Int!) {
@@ -25,6 +26,41 @@ mutation($project: ID!, $issue: ID!) {
2526}
2627` ;
2728
29+ export const PROJECT_FIELD_ID_QUERY : string = `
30+ query($project: ID!) {
31+ node(id: $project) {
32+ ... on ProjectV2 {
33+ fields(first: 20) {
34+ nodes {
35+ ... on ProjectV2Field {
36+ id
37+ name
38+ }
39+ ... on ProjectV2IterationField {
40+ id
41+ name
42+ configuration {
43+ iterations {
44+ startDate
45+ id
46+ }
47+ }
48+ }
49+ ... on ProjectV2SingleSelectField {
50+ id
51+ name
52+ options {
53+ id
54+ name
55+ }
56+ }
57+ }
58+ }
59+ }
60+ }
61+ }
62+ ` ;
63+
2864export const UPDATE_PROJECT_V2_ITEM_FIELD_VALUE_QUERY : string = `
2965mutation (
3066 $project: ID!
@@ -65,20 +101,38 @@ export class ProjectKit implements IProjectApi {
65101 private readonly logger : ILogger ,
66102 ) { }
67103
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- } */
104+ async changeIssueStateInProject ( issueCardId : string , project : NodeData , fields : FieldValues ) : Promise < void > {
105+ try {
106+ const op = await this . gql ( UPDATE_PROJECT_V2_ITEM_FIELD_VALUE_QUERY , {
107+ project : project . id ,
108+ item : issueCardId ,
109+ targetField : fields . field ,
110+ targetFieldValue : fields . value ,
111+ } ) ;
112+
113+ this . logger . debug ( "Returned " + JSON . stringify ( op ) ) ;
114+ } catch ( e ) {
115+ throw new Error ( "Failed while executing the 'UPDATE_PROJECT_V2_ITEM_FIELD_VALUE_QUERY' query" , { cause : e } ) ;
116+ }
117+ }
77118
78119 /**
79- * Fetches the node id from the project id and caches it.
80- * @returns node_id of the project. This value never changes so caching it per instance is effective
120+ * Get all the project fields in a project, with their node ids and available options
121+ * @returns A collection of all the fields available in the issue item
81122 */
123+ async getProjectFields ( projectId : string ) : Promise < FieldData [ ] > {
124+ try {
125+ type returnType = { node : { fields : { nodes : FieldData [ ] } } } ;
126+ const projectData = await this . gql < returnType > ( PROJECT_FIELD_ID_QUERY , { project : projectId } ) ;
127+
128+ this . logger . debug ( "correct node data: " + JSON . stringify ( projectData . node . fields . nodes [ 0 ] ) ) ;
129+ return projectData . node . fields . nodes ;
130+ } catch ( e ) {
131+ this . logger . error ( "Failed while executing the 'PROJECT_V2_QUERY' query" ) ;
132+ throw e ;
133+ }
134+ }
135+
82136 async fetchProjectData ( ) : Promise < NodeData > {
83137 if ( this . projectNode ) {
84138 return this . projectNode ;
@@ -95,8 +149,7 @@ export class ProjectKit implements IProjectApi {
95149
96150 return projectData . organization . projectV2 ;
97151 } catch ( e ) {
98- this . logger . error ( "Failed while executing the 'PROJECT_V2_QUERY' query" ) ;
99- throw e ;
152+ throw new Error ( "Failed while executing the 'PROJECT_V2_QUERY' query" , { cause : e } ) ;
100153 }
101154 }
102155
@@ -109,39 +162,55 @@ export class ProjectKit implements IProjectApi {
109162 await this . gql ( UPDATE_PROJECT_V2_ITEM_FIELD_VALUE_QUERY , { project, item, targetField, targetFieldValue } ) ;
110163 }
111164
112- async assignIssueToProject ( issue : Issue , projectId : string ) : Promise < boolean > {
165+ async assignIssueToProject ( issue : Issue , projectId : string ) : Promise < string > {
113166 try {
114167 const migration = await this . gql < { addProjectV2ItemById : { item : { id : string } } } > (
115168 ADD_PROJECT_V2_ITEM_BY_ID_QUERY ,
116169 { project : projectId , issue : issue . node_id } ,
117170 ) ;
118171
119- return ! ! migration . addProjectV2ItemById . item . id ;
172+ return migration . addProjectV2ItemById . item . id ;
120173 } catch ( e ) {
121- this . logger . error ( "Failed while executing 'ADD_PROJECT_V2_ITEM_BY_ID_QUERY' query" ) ;
122- throw e ;
174+ throw new Error ( "Failed while executing 'ADD_PROJECT_V2_ITEM_BY_ID_QUERY' query" , { cause : e } ) ;
123175 }
124176 }
125177
126- async addIssueToProject ( issue : Issue , project : NodeData ) : Promise < boolean > {
127- this . logger . info ( `Syncing issue #${ issue . number } for ${ project . title } ` ) ;
178+ async fetchProjectFieldNodeValues ( project : NodeData , projectFields ?: FieldValues ) : Promise < FieldValues > {
179+ if ( ! projectFields ) {
180+ throw new Error ( "'projectsFields' is null!" ) ;
181+ }
128182
129- return await this . assignIssueToProject ( issue , project . id ) ;
130- }
183+ const projectFieldData = await this . getProjectFields ( project . id ) ;
131184
132- async assignIssue ( issue : Issue ) : Promise < boolean > {
133- const project = await this . fetchProjectData ( ) ;
185+ const { field, value } = projectFields ;
134186
135- return await this . addIssueToProject ( issue , project ) ;
187+ // ? Should we use .localeCompare here?
188+ const customField = projectFieldData . find ( ( { name } ) => name . toUpperCase ( ) === field . toUpperCase ( ) ) ;
136189
137- // TODO: Assign targetField
138- }
190+ // check that this custom field exists and it has available options to set up
191+ if ( ! customField ) {
192+ throw new Error ( `Field ${ field } does not exist!` ) ;
193+ } else if ( ! customField . options ) {
194+ throw new Error ( `Field ${ field } does not have any available options!.` + "Please add options to set values" ) ;
195+ }
139196
140- async assignIssues ( issues : Issue [ ] ) : Promise < boolean [ ] > {
141- const project = await this . fetchProjectData ( ) ;
197+ this . logger . debug ( `Custom field '${ field } ' was found.` ) ;
142198
143- const issueAssigment = issues . map ( ( issue ) => this . addIssueToProject ( issue , project ) ) ;
199+ // search for the node element with the correct name.
200+ const fieldOption = customField . options . find ( ( { name } ) => name . toUpperCase ( ) === value . toUpperCase ( ) ) ;
201+ if ( ! fieldOption ) {
202+ const valuesArray = customField . options . map ( ( options ) => options . name ) ;
203+ throw new Error ( `Project value '${ value } ' does not exist. Available values are ${ JSON . stringify ( valuesArray ) } ` ) ;
204+ }
144205
145- return await Promise . all ( issueAssigment ) ;
206+ this . logger . debug ( `Field options '${ value } ' was found.` ) ;
207+
208+ return { field : customField . id , value : fieldOption . id } ;
209+ }
210+
211+ async assignIssue ( issue : Issue , project : NodeData ) : Promise < string > {
212+ this . logger . info ( `Syncing issue #${ issue . number } for ${ project . title } ` ) ;
213+
214+ return await this . assignIssueToProject ( issue , project . id ) ;
146215 }
147216}
0 commit comments