Skip to content

Commit

Permalink
Merge branch 'main' into update-key-shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
lucascosti authored Feb 1, 2022
2 parents b65bfda + daba2e0 commit 7c0a4af
Show file tree
Hide file tree
Showing 6,528 changed files with 199,811 additions and 180,642 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git/
node_modules/
.github/
.vscode/
docs/
script/
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package-lock.json @github/docs-engineering
package.json @github/docs-engineering

# Localization
/.github/actions-scripts/create-translation-batch-pr.js @github/docs-localization
/.github/workflows/create-translation-batch-pr.yml @github/docs-localization
/.github/workflows/crowdin.yml @github/docs-localization
/crowdin*.yml @github/docs-engineering @github/docs-localization
Expand Down
142 changes: 142 additions & 0 deletions .github/actions-scripts/create-translation-batch-pr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env node

import fs from 'fs'
import github from '@actions/github'

const OPTIONS = Object.fromEntries(
['BASE', 'BODY_FILE', 'GITHUB_TOKEN', 'HEAD', 'LANGUAGE', 'TITLE', 'GITHUB_REPOSITORY'].map(
(envVarName) => {
const envVarValue = process.env[envVarName]
if (!envVarValue) {
throw new Error(`You must supply a ${envVarName} environment variable`)
}
return [envVarName, envVarValue]
}
)
)

if (!process.env.GITHUB_REPOSITORY) {
throw new Error('GITHUB_REPOSITORY environment variable not set')
}

const RETRY_STATUSES = [
422, // Retry the operation if the PR already exists
502, // Retry the operation if the API responds with a `502 Bad Gateway` error.
]
const RETRY_ATTEMPTS = 3
const {
// One of the default environment variables provided by Actions.
GITHUB_REPOSITORY,

// These are passed in from the step in the workflow file.
TITLE,
BASE,
HEAD,
LANGUAGE,
BODY_FILE,
GITHUB_TOKEN,
} = OPTIONS
const [OWNER, REPO] = GITHUB_REPOSITORY.split('/')

const octokit = github.getOctokit(GITHUB_TOKEN)

/**
* @param {object} config Configuration options for finding the PR.
* @returns {Promise<number | undefined>} The PR number.
*/
async function findPullRequestNumber(config) {
// Get a list of PRs and see if one already exists.
const { data: listOfPullRequests } = await octokit.rest.pulls.list({
owner: config.owner,
repo: config.repo,
head: `${config.owner}:${config.head}`,
})

return listOfPullRequests[0]?.number
}

/**
* When this file was first created, we only introduced support for creating a pull request for some translation batch.
* However, some of our first workflow runs failed during the pull request creation due to a timeout error.
* There have been cases where, despite the timeout error, the pull request gets created _anyway_.
* To accommodate this reality, we created this function to look for an existing pull request before a new one is created.
* Although the "find" check is redundant in the first "cycle", it's designed this way to recursively call the function again via its retry mechanism should that be necessary.
*
* @param {object} config Configuration options for creating the pull request.
* @returns {Promise<number>} The PR number.
*/
async function findOrCreatePullRequest(config) {
const found = await findPullRequestNumber(config)

if (found) {
return found
}

try {
const { data: pullRequest } = await octokit.rest.pulls.create({
owner: config.owner,
repo: config.repo,
base: config.base,
head: config.head,
title: config.title,
body: config.body,
draft: false,
})

return pullRequest.number
} catch (error) {
if (!error.response || !config.retryCount) {
throw error
}

if (!config.retryStatuses.includes(error.response.status)) {
throw error
}

console.error(`Error creating pull request: ${error.message}`)
console.warn(`Retrying in 5 seconds...`)
await new Promise((resolve) => setTimeout(resolve, 5000))

config.retryCount -= 1

return findOrCreatePullRequest(config)
}
}

/**
* @param {object} config Configuration options for labeling the PR
* @returns {Promise<undefined>}
*/
async function labelPullRequest(config) {
await octokit.rest.issues.update({
owner: config.owner,
repo: config.repo,
issue_number: config.issue_number,
labels: config.labels,
})
}

async function main() {
const options = {
title: TITLE,
base: BASE,
head: HEAD,
body: fs.readFileSync(BODY_FILE, 'utf8'),
labels: ['translation-batch', `translation-batch-${LANGUAGE}`],
owner: OWNER,
repo: REPO,
retryStatuses: RETRY_STATUSES,
retryCount: RETRY_ATTEMPTS,
}

options.issue_number = await findOrCreatePullRequest(options)
const pr = `${GITHUB_REPOSITORY}#${options.issue_number}`
console.log(`Created PR ${pr}`)

// metadata parameters aren't currently available in `github.rest.pulls.create`,
// but they are in `github.rest.issues.update`.
await labelPullRequest(options)
console.log(`Updated ${pr} with these labels: ${options.labels.join(', ')}`)
}

main()
167 changes: 97 additions & 70 deletions .github/actions-scripts/fr-add-docs-reviewers-requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,88 +8,83 @@ import {
generateUpdateProjectNextItemFieldMutation,
} from './projects.js'

async function run() {
// Get info about the docs-content review board project
// and about open github/github PRs
const data = await graphql(
`
query ($organization: String!, $repo: String!, $projectNumber: Int!, $num_prs: Int!) {
organization(login: $organization) {
projectNext(number: $projectNumber) {
id
items(last: 100) {
async function getAllOpenPRs() {
let prsRemaining = true
let cursor
let prData = []
while (prsRemaining) {
const data = await graphql(
`
query ($organization: String!, $repo: String!) {
repository(name: $repo, owner: $organization) {
pullRequests(last: 100, states: OPEN${cursor ? ` before:"${cursor}"` : ''}) {
pageInfo{startCursor, hasPreviousPage},
nodes {
id
}
}
fields(first: 20) {
nodes {
id
name
settings
}
}
}
}
repository(name: $repo, owner: $organization) {
pullRequests(last: $num_prs, states: OPEN) {
nodes {
id
isDraft
reviewRequests(first: 10) {
nodes {
requestedReviewer {
... on Team {
name
isDraft
reviewRequests(first: 10) {
nodes {
requestedReviewer {
... on Team {
name
}
}
}
}
}
labels(first: 5) {
nodes {
name
labels(first: 5) {
nodes {
name
}
}
}
reviews(first: 10) {
nodes {
onBehalfOf(first: 1) {
nodes {
name
reviews(first: 10) {
nodes {
onBehalfOf(first: 1) {
nodes {
name
}
}
}
}
}
author {
login
author {
login
}
}
}
}
}
`,
{
organization: process.env.ORGANIZATION,
repo: process.env.REPO,
headers: {
authorization: `token ${process.env.TOKEN}`,
},
}
`,
{
organization: process.env.ORGANIZATION,
repo: process.env.REPO,
projectNumber: parseInt(process.env.PROJECT_NUMBER),
num_prs: parseInt(process.env.NUM_PRS),
headers: {
authorization: `token ${process.env.TOKEN}`,
'GraphQL-Features': 'projects_next_graphql',
},
}
)
)

prsRemaining = data.repository.pullRequests.pageInfo.hasPreviousPage
cursor = data.repository.pullRequests.pageInfo.startCursor
prData = [...prData, ...data.repository.pullRequests.nodes]
}

return prData
}

async function run() {
// Get info about open github/github PRs
const prData = await getAllOpenPRs()

// Get the PRs that are:
// - not draft
// - not a train
// - are requesting a review by docs-reviewers
// - have not already been reviewed on behalf of docs-reviewers
const prs = data.repository.pullRequests.nodes.filter(
const prs = prData.filter(
(pr) =>
!pr.isDraft &&
!pr.labels.nodes.find((label) => label.name === 'Deploy train 🚂') &&
pr.reviewRequests.nodes.find(
(requestedReviewers) => requestedReviewers.requestedReviewer.name === process.env.REVIEWER
(requestedReviewers) => requestedReviewers.requestedReviewer?.name === process.env.REVIEWER
) &&
!pr.reviews.nodes
.flatMap((review) => review.onBehalfOf.nodes)
Expand All @@ -104,28 +99,60 @@ async function run() {
const prAuthors = prs.map((pr) => pr.author.login)
console.log(`PRs found: ${prIDs}`)

// Get info about the docs-content review board project
const projectData = await graphql(
`
query ($organization: String!, $projectNumber: Int!) {
organization(login: $organization) {
projectNext(number: $projectNumber) {
id
items(last: 100) {
nodes {
id
}
}
fields(first: 100) {
nodes {
id
name
settings
}
}
}
}
}
`,
{
organization: process.env.ORGANIZATION,
projectNumber: parseInt(process.env.PROJECT_NUMBER),
headers: {
authorization: `token ${process.env.TOKEN}`,
},
}
)

// Get the project ID
const projectID = data.organization.projectNext.id
const projectID = projectData.organization.projectNext.id

// Get the IDs of the last 100 items on the board.
// Until we have a way to check from a PR whether the PR is in a project,
// this is how we (roughly) avoid overwriting PRs that are already on the board.
// If we are overwriting items, query for more items.
const existingItemIDs = data.organization.projectNext.items.nodes.map((node) => node.id)
const existingItemIDs = projectData.organization.projectNext.items.nodes.map((node) => node.id)

// Get the ID of the fields that we want to populate
const datePostedID = findFieldID('Date posted', data)
const reviewDueDateID = findFieldID('Review due date', data)
const statusID = findFieldID('Status', data)
const featureID = findFieldID('Feature', data)
const contributorTypeID = findFieldID('Contributor type', data)
const sizeTypeID = findFieldID('Size', data)
const authorID = findFieldID('Contributor', data)
const datePostedID = findFieldID('Date posted', projectData)
const reviewDueDateID = findFieldID('Review due date', projectData)
const statusID = findFieldID('Status', projectData)
const featureID = findFieldID('Feature', projectData)
const contributorTypeID = findFieldID('Contributor type', projectData)
const sizeTypeID = findFieldID('Size', projectData)
const authorID = findFieldID('Contributor', projectData)

// Get the ID of the single select values that we want to set
const readyForReviewID = findSingleSelectID('Ready for review', 'Status', data)
const hubberTypeID = findSingleSelectID('Hubber or partner', 'Contributor type', data)
const docsMemberTypeID = findSingleSelectID('Docs team', 'Contributor type', data)
const readyForReviewID = findSingleSelectID('Ready for review', 'Status', projectData)
const hubberTypeID = findSingleSelectID('Hubber or partner', 'Contributor type', projectData)
const docsMemberTypeID = findSingleSelectID('Docs team', 'Contributor type', projectData)

// Add the PRs to the project
const itemIDs = await addItemsToProject(prIDs, projectID)
Expand Down
Loading

0 comments on commit 7c0a4af

Please sign in to comment.