| | |
| | import { graphql } from '@octokit/graphql' |
| |
|
| | |
| |
|
| | |
| | export function findFieldID(fieldName: string, data: Record<string, any>) { |
| | const field = data.organization.projectV2.fields.nodes.find( |
| | (fieldNode: Record<string, any>) => fieldNode.name === fieldName, |
| | ) |
| |
|
| | if (field && field.id) { |
| | return field.id |
| | } else { |
| | throw new Error(`A field called "${fieldName}" was not found. Check if the field was renamed.`) |
| | } |
| | } |
| |
|
| | |
| | export function findSingleSelectID( |
| | singleSelectName: string, |
| | fieldName: string, |
| | data: Record<string, any>, |
| | ) { |
| | const field = data.organization.projectV2.fields.nodes.find( |
| | (fieldData: Record<string, any>) => fieldData.name === fieldName, |
| | ) |
| | if (!field) { |
| | throw new Error(`A field called "${fieldName}" was not found. Check if the field was renamed.`) |
| | } |
| |
|
| | const singleSelect = field.options.find( |
| | (option: Record<string, any>) => option.name === singleSelectName, |
| | ) |
| |
|
| | if (singleSelect && singleSelect.id) { |
| | return singleSelect.id |
| | } else { |
| | throw new Error( |
| | `A single select called "${singleSelectName}" for the field "${fieldName}" was not found. Check if the single select was renamed.`, |
| | ) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export async function addItemsToProject(items: string[], project: string) { |
| | console.log(`Adding ${items} to project ${project}`) |
| |
|
| | const mutations = items.map( |
| | (item, index) => ` |
| | item_${index}: addProjectV2ItemById(input: { |
| | projectId: $project |
| | contentId: "${item}" |
| | }) { |
| | item { |
| | id |
| | } |
| | } |
| | `, |
| | ) |
| |
|
| | const mutation = ` |
| | mutation($project:ID!) { |
| | ${mutations.join(' ')} |
| | } |
| | ` |
| |
|
| | const newItems: Record<string, any> = await graphql(mutation, { |
| | project, |
| | headers: { |
| | authorization: `token ${process.env.TOKEN}`, |
| | }, |
| | }) |
| |
|
| | |
| | |
| | |
| |
|
| | const newItemIDs = Object.entries(newItems).map((item) => item[1].item.id) |
| |
|
| | return newItemIDs |
| | } |
| |
|
| | export async function addItemToProject(item: string, project: string) { |
| | const newItemIDs = await addItemsToProject([item], project) |
| |
|
| | const newItemID = newItemIDs[0] |
| |
|
| | return newItemID |
| | } |
| |
|
| | |
| | |
| | export async function isDocsTeamMember(login: string) { |
| | |
| | if (login === 'docs-bot' || login === 'copilot') { |
| | return true |
| | } |
| | |
| | const data: Record<string, any> = await graphql( |
| | ` |
| | query { |
| | organization(login: "github") { |
| | team(slug: "docs") { |
| | members { |
| | nodes { |
| | login |
| | } |
| | } |
| | } |
| | } |
| | } |
| | `, |
| | { |
| | headers: { |
| | authorization: `token ${process.env.TOKEN}`, |
| | }, |
| | }, |
| | ) |
| |
|
| | const teamMembers = data.organization.team.members.nodes.map( |
| | (entry: Record<string, any>) => entry.login, |
| | ) |
| |
|
| | return teamMembers.includes(login) |
| | } |
| |
|
| | |
| | |
| | export async function isGitHubOrgMember(login: string) { |
| | const data: Record<string, any> = await graphql( |
| | ` |
| | query { |
| | user(login: "${login}") { |
| | organization(login: "github"){ |
| | name |
| | } |
| | } |
| | } |
| | `, |
| | { |
| | headers: { |
| | authorization: `token ${process.env.TOKEN}`, |
| | }, |
| | }, |
| | ) |
| |
|
| | return Boolean(data.user.organization) |
| | } |
| |
|
| | |
| | export function formatDateForProject(date: Date) { |
| | return date.toISOString() |
| | } |
| |
|
| | |
| | |
| | |
| | export function calculateDueDate(datePosted: Date, turnaround = 2) { |
| | let daysUntilDue |
| | switch (datePosted.getDay()) { |
| | case 4: |
| | daysUntilDue = turnaround + 2 |
| | break |
| | case 5: |
| | daysUntilDue = turnaround + 2 |
| | break |
| | case 6: |
| | daysUntilDue = turnaround + 1 |
| | break |
| | default: |
| | daysUntilDue = turnaround |
| | } |
| | const millisecPerDay = 24 * 60 * 60 * 1000 |
| | const dueDate = new Date(datePosted.getTime() + millisecPerDay * daysUntilDue) |
| | return dueDate |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function generateUpdateProjectV2ItemFieldMutation({ |
| | item, |
| | author, |
| | turnaround = 2, |
| | feature = '', |
| | }: { |
| | item: string |
| | author: string |
| | turnaround?: number |
| | feature?: string |
| | }) { |
| | const datePosted = new Date() |
| | const dueDate = calculateDueDate(datePosted, turnaround) |
| |
|
| | |
| | |
| | function generateMutationToUpdateField({ |
| | item: itemId, |
| | fieldID, |
| | value, |
| | fieldType, |
| | literal = false, |
| | }: { |
| | item: string |
| | fieldID: string |
| | value: string |
| | fieldType: string |
| | literal?: boolean |
| | }) { |
| | const parsedValue = literal ? `${fieldType}: "${value}"` : `${fieldType}: ${value}` |
| |
|
| | |
| | |
| | return ` |
| | set_${fieldID.slice(1)}_item_${itemId.replaceAll( |
| | /[^a-z0-9]/g, |
| | '', |
| | )}: updateProjectV2ItemFieldValue(input: { |
| | projectId: $project |
| | itemId: "${itemId}" |
| | fieldId: ${fieldID} |
| | value: { ${parsedValue} } |
| | }) { |
| | projectV2Item { |
| | id |
| | } |
| | } |
| | ` |
| | } |
| |
|
| | const mutation = ` |
| | mutation( |
| | $project: ID! |
| | $statusID: ID! |
| | $statusValueID: String! |
| | $datePostedID: ID! |
| | $reviewDueDateID: ID! |
| | $contributorTypeID: ID! |
| | $contributorType: String! |
| | $sizeTypeID: ID! |
| | $sizeType: String! |
| | $featureID: ID! |
| | $authorID: ID! |
| | ) { |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$statusID', |
| | value: '$statusValueID', |
| | fieldType: 'singleSelectOptionId', |
| | })} |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$datePostedID', |
| | value: formatDateForProject(datePosted), |
| | fieldType: 'date', |
| | literal: true, |
| | })} |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$reviewDueDateID', |
| | value: formatDateForProject(dueDate), |
| | fieldType: 'date', |
| | literal: true, |
| | })} |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$contributorTypeID', |
| | value: '$contributorType', |
| | fieldType: 'singleSelectOptionId', |
| | })} |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$sizeTypeID', |
| | value: '$sizeType', |
| | fieldType: 'singleSelectOptionId', |
| | })} |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$featureID', |
| | value: feature, |
| | fieldType: 'text', |
| | literal: true, |
| | })} |
| | ${generateMutationToUpdateField({ |
| | item, |
| | fieldID: '$authorID', |
| | value: author, |
| | fieldType: 'text', |
| | literal: true, |
| | })} |
| | } |
| | ` |
| |
|
| | return mutation |
| | } |
| |
|
| | |
| | export function getFeature(data: Record<string, any>) { |
| | |
| | if (data.item.__typename !== 'PullRequest') { |
| | return '' |
| | } |
| |
|
| | const paths = data.item.files.nodes.map((node: Record<string, any>) => node.path) |
| |
|
| | |
| | |
| | |
| | |
| | if ( |
| | process.env.REPO === 'github/docs-internal' || |
| | process.env.REPO === 'github/docs' || |
| | process.env.REPO === 'github/docs-early-access' |
| | ) { |
| | const features: Set<string> = new Set([]) |
| | for (const path of paths as string[]) { |
| | const pathComponents = path.split('/') |
| | if (pathComponents[0] === 'content') { |
| | features.add(pathComponents[1]) |
| | } |
| | } |
| | const feature = Array.from(features).join() |
| |
|
| | return feature |
| | } |
| |
|
| | |
| | if (process.env.REPO === 'github/github') { |
| | const features: Set<string> = new Set([]) |
| | if (paths.some((path: string) => path.startsWith('app/api/description'))) { |
| | features.add('OpenAPI') |
| | for (const path of paths as string[]) { |
| | if (path.startsWith('app/api/description/operations')) { |
| | features.add(path.split('/')[4]) |
| | features.add('rest') |
| | } |
| | if (path.startsWith('app/api/description/webhooks')) { |
| | features.add(path.split('/')[4]) |
| | features.add('webhooks') |
| | } |
| | if (path.startsWith('app/api/description/components/schemas/webhooks')) { |
| | features.add('webhooks') |
| | } |
| | } |
| | } |
| |
|
| | const feature = Array.from(features).join() |
| |
|
| | return feature |
| | } |
| |
|
| | if (process.env.REPO === 'github/docs-strategy') { |
| | return 'CD plan' |
| | } |
| |
|
| | return '' |
| | } |
| |
|
| | |
| | export function getSize(data: Record<string, any>) { |
| | |
| | if (data.item.__typename !== 'PullRequest') { |
| | return 'S' |
| | } |
| |
|
| | |
| | if (process.env.REPO === 'github/github') { |
| | let numFiles = 0 |
| | let numChanges = 0 |
| | for (const node of data.item.files.nodes as Record<string, any>[]) { |
| | if (node.path.startsWith('app/api/description')) { |
| | numFiles += 1 |
| | numChanges += node.additions |
| | numChanges += node.deletions |
| | } |
| | } |
| | if (numFiles < 5 && numChanges < 10) { |
| | return 'XS' |
| | } else if (numFiles < 10 && numChanges < 50) { |
| | return 'S' |
| | } else if (numFiles < 10 && numChanges < 250) { |
| | return 'M' |
| | } else { |
| | return 'L' |
| | } |
| | } else { |
| | |
| | let numFiles = 0 |
| | let numChanges = 0 |
| | for (const node of data.item.files.nodes as Record<string, any>[]) { |
| | numFiles += 1 |
| | numChanges += node.additions |
| | numChanges += node.deletions |
| | } |
| | if (numFiles < 5 && numChanges < 10) { |
| | return 'XS' |
| | } else if (numFiles < 10 && numChanges < 50) { |
| | return 'S' |
| | } else if (numFiles < 10 && numChanges < 250) { |
| | return 'M' |
| | } else { |
| | return 'L' |
| | } |
| | } |
| | } |
| |
|
| | export default { |
| | addItemsToProject, |
| | addItemToProject, |
| | isDocsTeamMember, |
| | isGitHubOrgMember, |
| | findFieldID, |
| | findSingleSelectID, |
| | formatDateForProject, |
| | calculateDueDate, |
| | generateUpdateProjectV2ItemFieldMutation, |
| | getFeature, |
| | getSize, |
| | } |
| |
|