-
Notifications
You must be signed in to change notification settings - Fork 94
/
add-to-project.ts
169 lines (143 loc) · 4.91 KB
/
add-to-project.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import * as core from '@actions/core'
import * as github from '@actions/github'
const urlParse = /\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/
interface ProjectNodeIDResponse {
organization?: {
projectV2: {
id: string
}
}
user?: {
projectV2: {
id: string
}
}
}
interface ProjectAddItemResponse {
addProjectV2ItemById: {
item: {
id: string
}
}
}
interface ProjectV2AddDraftIssueResponse {
addProjectV2DraftIssue: {
projectItem: {
id: string
}
}
}
export async function addToProject(): Promise<void> {
const projectUrl = core.getInput('project-url', {required: true})
const ghToken = core.getInput('github-token', {required: true})
const labeled =
core
.getInput('labeled')
.split(',')
.map(l => l.trim().toLowerCase())
.filter(l => l.length > 0) ?? []
const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase()
const octokit = github.getOctokit(ghToken)
const issue = github.context.payload.issue ?? github.context.payload.pull_request
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name.toLowerCase())
const issueOwnerName = github.context.payload.repository?.owner.login
core.debug(`Issue/PR owner: ${issueOwnerName}`)
core.debug(`Issue/PR labels: ${issueLabels.join(', ')}`)
// Ensure the issue matches our `labeled` filter based on the label-operator.
if (labelOperator === 'and') {
if (!labeled.every(l => issueLabels.includes(l))) {
core.info(`Skipping issue ${issue?.number} because it doesn't match all the labels: ${labeled.join(', ')}`)
return
}
} else if (labelOperator === 'not') {
if (labeled.length > 0 && issueLabels.some(l => labeled.includes(l))) {
core.info(`Skipping issue ${issue?.number} because it contains one of the labels: ${labeled.join(', ')}`)
return
}
} else {
if (labeled.length > 0 && !issueLabels.some(l => labeled.includes(l))) {
core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`)
return
}
}
core.debug(`Project URL: ${projectUrl}`)
const urlMatch = projectUrl.match(urlParse)
if (!urlMatch) {
throw new Error(
`Invalid project URL: ${projectUrl}. Project URL should match the format <GitHub server domain name>/<orgs-or-users>/<ownerName>/projects/<projectNumber>`,
)
}
const projectOwnerName = urlMatch.groups?.ownerName
const projectNumber = parseInt(urlMatch.groups?.projectNumber ?? '', 10)
const ownerType = urlMatch.groups?.ownerType
const ownerTypeQuery = mustGetOwnerTypeQuery(ownerType)
core.debug(`Project owner: ${projectOwnerName}`)
core.debug(`Project number: ${projectNumber}`)
core.debug(`Project owner type: ${ownerType}`)
// First, use the GraphQL API to request the project's node ID.
const idResp = await octokit.graphql<ProjectNodeIDResponse>(
`query getProject($projectOwnerName: String!, $projectNumber: Int!) {
${ownerTypeQuery}(login: $projectOwnerName) {
projectV2(number: $projectNumber) {
id
}
}
}`,
{
projectOwnerName,
projectNumber,
},
)
const projectId = idResp[ownerTypeQuery]?.projectV2.id
const contentId = issue?.node_id
core.debug(`Project node ID: ${projectId}`)
core.debug(`Content ID: ${contentId}`)
// Next, use the GraphQL API to add the issue to the project.
// If the issue has the same owner as the project, we can directly
// add a project item. Otherwise, we add a draft issue.
if (issueOwnerName === projectOwnerName) {
core.info('Creating project item')
const addResp = await octokit.graphql<ProjectAddItemResponse>(
`mutation addIssueToProject($input: AddProjectV2ItemByIdInput!) {
addProjectV2ItemById(input: $input) {
item {
id
}
}
}`,
{
input: {
projectId,
contentId,
},
},
)
core.setOutput('itemId', addResp.addProjectV2ItemById.item.id)
} else {
core.info('Creating draft issue in project')
const addResp = await octokit.graphql<ProjectV2AddDraftIssueResponse>(
`mutation addDraftIssueToProject($projectId: ID!, $title: String!) {
addProjectV2DraftIssue(input: {
projectId: $projectId,
title: $title
}) {
projectItem {
id
}
}
}`,
{
projectId,
title: issue?.html_url,
},
)
core.setOutput('itemId', addResp.addProjectV2DraftIssue.projectItem.id)
}
}
export function mustGetOwnerTypeQuery(ownerType?: string): 'organization' | 'user' {
const ownerTypeQuery = ownerType === 'orgs' ? 'organization' : ownerType === 'users' ? 'user' : null
if (!ownerTypeQuery) {
throw new Error(`Unsupported ownerType: ${ownerType}. Must be one of 'orgs' or 'users'`)
}
return ownerTypeQuery
}