mirror of https://github.com/rancher/dashboard.git
388 lines
8.6 KiB
JavaScript
388 lines
8.6 KiB
JavaScript
const USER_AGENT = 'PR Action'
|
|
const TOKEN = process.env.TOKEN || process.env.GH_TOKEN;
|
|
const GRAPHQL = 'https://api.github.com/graphql';
|
|
|
|
const https = require('https');
|
|
|
|
/**
|
|
* Get the Project Information for the specified GH project
|
|
*
|
|
* @param org GitHub Organization
|
|
* @param num Project number
|
|
* @returns Project Info (ID and Status Field info)
|
|
*/
|
|
async function ghProject(org, num) {
|
|
let orgOrUser = org;
|
|
|
|
if (orgOrUser.startsWith('@')) {
|
|
orgOrUser = orgOrUser.substr(1);
|
|
}
|
|
|
|
const type = orgOrUser !== org ? 'user' : 'organization';
|
|
|
|
const gQL = `query {
|
|
${ type }(login: "${ orgOrUser }") {
|
|
projectV2(number: ${ num }) {
|
|
id
|
|
fields(first:100) {
|
|
nodes {
|
|
... on ProjectV2SingleSelectField {
|
|
id
|
|
name
|
|
options {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
... on ProjectV2FieldCommon {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`;
|
|
|
|
// console.log(gQL);
|
|
|
|
const res = await graphql(gQL);
|
|
|
|
const prj = {};
|
|
|
|
if (res.data?.organization?.projectV2) {
|
|
const v2Project = res.data?.organization?.projectV2;
|
|
|
|
prj.id = v2Project.id;
|
|
|
|
const statusField = v2Project.fields?.nodes.find((node) => node.name === 'Status');
|
|
|
|
if (statusField) {
|
|
const optionMap = {};
|
|
statusField.options.forEach((opt) => {
|
|
optionMap[opt.name] = opt.id;
|
|
});
|
|
|
|
prj.statusField = {
|
|
id: statusField.id,
|
|
options: optionMap
|
|
};
|
|
}
|
|
|
|
const storyPointsField = v2Project.fields?.nodes.find((node) => node.name === 'Story Points');
|
|
|
|
if (storyPointsField) {
|
|
prj.storyPointsField = {
|
|
id: storyPointsField.id
|
|
};
|
|
}
|
|
|
|
} else {
|
|
console.log(res);
|
|
}
|
|
|
|
return prj;
|
|
}
|
|
/**
|
|
* Fetch the issue and get the project info for the issue (map of project ID to project issue ID)
|
|
*
|
|
* @param org GitHub Organization
|
|
* @param repo GitHub Repository
|
|
* @param num Project number
|
|
* @returns Project Issue Map metadata
|
|
*/
|
|
async function ghProjectIssue(org, repo, num) {
|
|
const gQL = `query {
|
|
repository(name:"${ repo }", owner:"${ org }") {
|
|
issue(number: ${ num }) {
|
|
title,
|
|
labels(first: 100) {
|
|
nodes {
|
|
name
|
|
}
|
|
}
|
|
projectItems(first:100){
|
|
nodes {
|
|
id
|
|
project {
|
|
id
|
|
title
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`;
|
|
|
|
const res = await graphql(gQL);
|
|
|
|
if (res.data?.repository?.issue?.projectItems) {
|
|
const projectItems = res.data?.repository?.issue?.projectItems?.nodes;
|
|
const projectMap = {};
|
|
|
|
// Map of project ID to the Issue ID within that project
|
|
projectItems.forEach((item) => {
|
|
projectMap[item.project.id] = item.id;
|
|
});
|
|
|
|
return projectMap;
|
|
} else {
|
|
console.log('No project items');
|
|
console.log(JSON.stringify(res, null, 2));
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Change the status of an issue on a project board
|
|
*
|
|
* @param prj Project metadata
|
|
* @param prjIssueID Issue ID in project
|
|
* @param status New status name
|
|
* @returns
|
|
*/
|
|
async function ghUpdateProjectIssueStatus(prj, prjIssueID, status) {
|
|
// Check status exists
|
|
const statusOptionID = prj.statusField?.options?.[status];
|
|
|
|
if (!statusOptionID) {
|
|
console.log(`Can not find status ${ status } as a valid option for the project's status field`);
|
|
|
|
return false;
|
|
}
|
|
|
|
const gQL = `mutation {
|
|
updateProjectV2ItemFieldValue(
|
|
input: {
|
|
projectId: "${ prj.id }"
|
|
itemId: "${ prjIssueID }"
|
|
fieldId: "${ (prj.statusField.id) }"
|
|
value: {
|
|
singleSelectOptionId: "${ statusOptionID }"
|
|
}
|
|
}
|
|
) {
|
|
projectV2Item {
|
|
id
|
|
}
|
|
}
|
|
}`;
|
|
|
|
return await graphql(gQL);
|
|
}
|
|
|
|
/**
|
|
* Add an issue to the GitHub Project boar4d
|
|
* @param {*} prj Project metadata
|
|
* @param {*} issue Issue metadata
|
|
* @returns Response from add request
|
|
*/
|
|
async function ghAddIssueToProject(prj, issue) {
|
|
const gQL = `mutation {
|
|
addProjectV2ItemById(input: {projectId: "${ prj.id }" contentId: "${ issue.node_id }"}) {
|
|
item {
|
|
id
|
|
}
|
|
}
|
|
}`;
|
|
|
|
return await graphql(gQL);
|
|
}
|
|
|
|
/**
|
|
* Fetch the open issues for the given org/repo with the given milestone and label
|
|
*
|
|
*/
|
|
async function ghFetchOpenIssues(org, repo, milestone, label, previous) {
|
|
let extra = milestone ? `milestone:${ milestone }` : '';
|
|
|
|
if (label) {
|
|
extra += ` label:\\"${label}\\"`;
|
|
}
|
|
|
|
const query = `repo:${org}/${repo} ${extra}`;
|
|
|
|
return ghQueryIssues(query, previous);
|
|
}
|
|
|
|
async function ghFetchOpenIssuesInProject(org, projectId, milestone, label, previous) {
|
|
let extra = milestone ? `milestone:${ milestone }` : '';
|
|
|
|
if (label) {
|
|
extra += ` label:\\"${label}\\"`;
|
|
}
|
|
|
|
const query = `project:${org}/${projectId} ${extra}`;
|
|
|
|
return ghQueryIssues(query, previous);
|
|
}
|
|
|
|
async function ghQueryIssues(query, previous) {
|
|
let after = '';
|
|
|
|
if (previous && previous.pageInfo?.endCursor) {
|
|
after = `after: "${ previous.pageInfo.endCursor }",`;
|
|
}
|
|
|
|
const gQL = `query {
|
|
search(first:100, ${after} type:ISSUE, query:"is:open is:issue ${query}") {
|
|
issueCount,
|
|
pageInfo {
|
|
startCursor
|
|
hasNextPage
|
|
endCursor
|
|
}
|
|
nodes {
|
|
...on Issue {
|
|
id
|
|
number
|
|
state
|
|
title
|
|
labels(first: 100) {
|
|
nodes {
|
|
name
|
|
}
|
|
}
|
|
projectItems(first:100) {
|
|
totalCount
|
|
nodes {
|
|
id
|
|
project {
|
|
id
|
|
}
|
|
status: fieldValueByName(name: "Status") {
|
|
...on ProjectV2ItemFieldSingleSelectValue {
|
|
name
|
|
id
|
|
}
|
|
}
|
|
storyPoints: fieldValueByName(name: "Story Points") {
|
|
...on ProjectV2ItemFieldNumberValue {
|
|
number
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`;
|
|
|
|
const res = await graphql(gQL);
|
|
|
|
if (res.data?.search) {
|
|
if (previous) {
|
|
// Copy in the previous issues
|
|
res.data.search.nodes = [
|
|
...previous.nodes,
|
|
...res.data.search.nodes,
|
|
];
|
|
}
|
|
|
|
if (res.data.search.pageInfo.hasNextPage) {
|
|
return await ghQueryIssues(query, res.data.search)
|
|
}
|
|
|
|
return res.data.search.nodes;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function fetch (url) {
|
|
const opts = {
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Authorization': `token ${TOKEN}`
|
|
}
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, opts, (response) => {
|
|
let chunks_of_data = [];
|
|
|
|
response.on('data', (fragments) => {
|
|
chunks_of_data.push(fragments);
|
|
});
|
|
|
|
response.on('end', () => {
|
|
let response_body = Buffer.concat(chunks_of_data);
|
|
resolve(JSON.parse(response_body.toString()));
|
|
});
|
|
|
|
response.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
function post(url, data) {
|
|
return write(url, data, 'POST');
|
|
}
|
|
|
|
function put(url, data) {
|
|
return write(url, data, 'PUT');
|
|
}
|
|
|
|
function patch(url, data) {
|
|
return write(url, data, 'PATCH');
|
|
}
|
|
|
|
function graphql(data) {
|
|
return write(GRAPHQL, { query: data }, 'POST')
|
|
}
|
|
|
|
function write(url, data, method) {
|
|
const json = JSON.stringify(data);
|
|
const opts = {
|
|
method: method || 'POST',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Authorization': `token ${TOKEN}`,
|
|
'Content-Type': 'application/json',
|
|
'Content-Length': json.length
|
|
}
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const req = https.request(url, opts, (response) => {
|
|
let chunks_of_data = [];
|
|
|
|
response.on('data', (fragments) => {
|
|
chunks_of_data.push(fragments);
|
|
});
|
|
|
|
response.on('end', () => {
|
|
let response_body = Buffer.concat(chunks_of_data);
|
|
try {
|
|
resolve(JSON.parse(response_body.toString()));
|
|
} catch (e) {
|
|
reject(response);
|
|
}
|
|
});
|
|
|
|
response.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
});
|
|
req.write(json);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
fetch,
|
|
post,
|
|
put,
|
|
patch,
|
|
graphql,
|
|
ghProject,
|
|
ghProjectIssue,
|
|
ghUpdateProjectIssueStatus,
|
|
ghFetchOpenIssues,
|
|
ghAddIssueToProject,
|
|
ghFetchOpenIssuesInProject,
|
|
};
|