349 lines
14 KiB
YAML
349 lines
14 KiB
YAML
name: Doc Review
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, ready_for_review]
|
|
paths:
|
|
- 'content/**'
|
|
- 'layouts/**'
|
|
- 'assets/**'
|
|
- 'data/**'
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr_number:
|
|
description: 'PR number to review'
|
|
required: true
|
|
type: number
|
|
|
|
permissions: {}
|
|
|
|
concurrency:
|
|
group: doc-review-${{ github.event.number || inputs.pr_number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# -----------------------------------------------------------------
|
|
# Job 1: Resolve preview URLs from changed content files
|
|
# -----------------------------------------------------------------
|
|
resolve-urls:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
if: |
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(!github.event.pull_request.draft &&
|
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
!contains(github.event.pull_request.labels.*.name, 'skip-review'))
|
|
outputs:
|
|
urls: ${{ steps.detect.outputs.urls }}
|
|
url-count: ${{ steps.detect.outputs.url-count }}
|
|
skipped: ${{ steps.detect.outputs.skipped }}
|
|
skip-reason: ${{ steps.detect.outputs.skip-reason }}
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
with:
|
|
persist-credentials: false
|
|
fetch-depth: 0
|
|
sparse-checkout: |
|
|
content
|
|
data/products.yml
|
|
scripts/lib/content-utils.js
|
|
.github/scripts/resolve-review-urls.js
|
|
package.json
|
|
sparse-checkout-cone-mode: false
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Resolve base ref
|
|
id: base
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
run: |
|
|
if [ -n "${{ github.base_ref }}" ]; then
|
|
echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
|
|
else
|
|
BASE=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json baseRefName -q .baseRefName)
|
|
git fetch origin "$BASE"
|
|
echo "ref=origin/$BASE" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Detect changed pages
|
|
id: detect
|
|
env:
|
|
BASE_REF: ${{ steps.base.outputs.ref }}
|
|
run: node .github/scripts/resolve-review-urls.js
|
|
|
|
# -----------------------------------------------------------------
|
|
# Job 2: Copilot code review (runs in parallel with Job 1)
|
|
# -----------------------------------------------------------------
|
|
copilot-review:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
if: |
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(!github.event.pull_request.draft &&
|
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
!contains(github.event.pull_request.labels.*.name, 'skip-review'))
|
|
steps:
|
|
- name: Request Copilot review
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
with:
|
|
script: |
|
|
const prNumber = context.issue.number || Number(process.env.PR_NUMBER);
|
|
try {
|
|
await github.rest.pulls.requestReviewers({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
reviewers: ['copilot-pull-request-reviewer'],
|
|
});
|
|
core.info('Copilot code review requested successfully');
|
|
} catch (error) {
|
|
core.warning(`Could not request Copilot review: ${error.message}`);
|
|
core.warning(
|
|
'To enable automatic Copilot reviews, configure a repository ruleset: ' +
|
|
'Settings → Rules → Rulesets → "Automatically request Copilot code review"'
|
|
);
|
|
}
|
|
|
|
# -----------------------------------------------------------------
|
|
# Job 3: Visual review check run (depends on Job 1 for URLs)
|
|
#
|
|
# Creates a GitHub Check Run (visible in the Checks tab) instead of
|
|
# posting a PR comment mentioning @github-copilot. A comment mention
|
|
# alone never invokes Copilot Vision; only a proper check run surfaces
|
|
# the visual-review status where reviewers can act on it.
|
|
# -----------------------------------------------------------------
|
|
copilot-visual-review:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
checks: write
|
|
needs: resolve-urls
|
|
if: needs.resolve-urls.result == 'success' && fromJson(needs.resolve-urls.outputs.url-count) > 0
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
with:
|
|
persist-credentials: false
|
|
sparse-checkout: .github/prompts/copilot-visual-review.md
|
|
sparse-checkout-cone-mode: false
|
|
|
|
- name: Get PR head SHA
|
|
id: pr-sha
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
with:
|
|
script: |
|
|
let sha = context.payload.pull_request?.head?.sha;
|
|
if (!sha) {
|
|
const prNumber = context.issue.number || Number(process.env.PR_NUMBER);
|
|
const { data: pr } = await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
});
|
|
sha = pr.head.sha;
|
|
}
|
|
core.setOutput('sha', sha);
|
|
|
|
- name: Create in-progress check run
|
|
id: create-check
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
HEAD_SHA: ${{ steps.pr-sha.outputs.sha }}
|
|
with:
|
|
script: |
|
|
const { data: check } = await github.rest.checks.create({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: 'Visual Review',
|
|
head_sha: process.env.HEAD_SHA,
|
|
status: 'in_progress',
|
|
started_at: new Date().toISOString(),
|
|
output: {
|
|
title: 'Visual Review — Waiting for preview deployment',
|
|
summary: 'Waiting for PR preview to be available…',
|
|
},
|
|
});
|
|
core.setOutput('check-run-id', String(check.id));
|
|
core.info(`Created check run ${check.id}`);
|
|
|
|
- name: Wait for preview deployment
|
|
id: wait
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
run: |
|
|
PREVIEW_URL="https://influxdata.github.io/docs-v2/pr-preview/pr-${PR_NUMBER}/"
|
|
TIMEOUT=600 # 10 minutes
|
|
INTERVAL=15
|
|
ELAPSED=0
|
|
|
|
echo "Waiting for preview at ${PREVIEW_URL}"
|
|
|
|
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
|
|
STATUS=$(curl -s -o /dev/null -L -w "%{http_code}" "$PREVIEW_URL" || echo "000")
|
|
if [ "$STATUS" = "200" ]; then
|
|
echo "Preview is live"
|
|
echo "available=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
echo "Status: ${STATUS} (${ELAPSED}s / ${TIMEOUT}s)"
|
|
sleep "$INTERVAL"
|
|
ELAPSED=$((ELAPSED + INTERVAL))
|
|
done
|
|
|
|
echo "Preview deployment timed out after ${TIMEOUT}s"
|
|
echo "available=false" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Complete check run — preview available
|
|
if: steps.wait.outputs.available == 'true'
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
PREVIEW_URLS: ${{ needs.resolve-urls.outputs.urls }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
CHECK_RUN_ID: ${{ steps.create-check.outputs.check-run-id }}
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const checkRunId = Number(process.env.CHECK_RUN_ID);
|
|
const prNumber = context.issue.number || Number(process.env.PR_NUMBER);
|
|
const previewBase = `https://influxdata.github.io/docs-v2/pr-preview/pr-${prNumber}`;
|
|
|
|
let urls = [];
|
|
try { urls = JSON.parse(process.env.PREVIEW_URLS); } catch {}
|
|
|
|
const checklist = fs.readFileSync(
|
|
'.github/prompts/copilot-visual-review.md',
|
|
'utf8'
|
|
);
|
|
const pageList = urls.slice(0, 20)
|
|
.map(u => `- [${u}](${previewBase}${u})`)
|
|
.join('\n')
|
|
+ (urls.length > 20 ? `\n- … and ${urls.length - 20} more` : '');
|
|
|
|
await github.rest.checks.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
check_run_id: checkRunId,
|
|
status: 'completed',
|
|
conclusion: 'neutral',
|
|
completed_at: new Date().toISOString(),
|
|
output: {
|
|
title: `Visual Review — ${urls.length} page(s) ready for review`,
|
|
summary: `Preview is live with ${urls.length} changed page(s). Complete the checklist in the details to finish visual review.`,
|
|
text: `## Pages to Review\n\n${pageList}\n\n## Visual Review Checklist\n\n${checklist.trim()}`,
|
|
},
|
|
});
|
|
core.info(`Completed check run ${checkRunId} — ${urls.length} pages ready`);
|
|
|
|
- name: Complete check run — timed out
|
|
if: steps.wait.outputs.available == 'false'
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
CHECK_RUN_ID: ${{ steps.create-check.outputs.check-run-id }}
|
|
with:
|
|
script: |
|
|
const checkRunId = Number(process.env.CHECK_RUN_ID);
|
|
const prNumber = context.issue.number || Number(process.env.PR_NUMBER);
|
|
const previewUrl = `https://influxdata.github.io/docs-v2/pr-preview/pr-${prNumber}/`;
|
|
|
|
await github.rest.checks.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
check_run_id: checkRunId,
|
|
status: 'completed',
|
|
conclusion: 'neutral',
|
|
completed_at: new Date().toISOString(),
|
|
output: {
|
|
title: 'Visual Review — Preview deployment timed out',
|
|
summary: [
|
|
'Preview was not available within 10 minutes.',
|
|
'',
|
|
'To trigger visual review:',
|
|
`1. Wait for the [PR Preview](${previewUrl}) to deploy`,
|
|
'2. Re-run this workflow from the Actions tab',
|
|
].join('\n'),
|
|
},
|
|
});
|
|
core.info(`Completed check run ${checkRunId} — timed out`);
|
|
|
|
# -----------------------------------------------------------------
|
|
# Job 4: Report when visual review is skipped (no URLs to review)
|
|
#
|
|
# Creates a GitHub Check Run with conclusion 'skipped' so the Checks
|
|
# tab shows an accurate status rather than simply omitting the entry.
|
|
# -----------------------------------------------------------------
|
|
report-skipped:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: read
|
|
checks: write
|
|
needs: resolve-urls
|
|
# GitHub Actions outputs are always strings; 'true' string comparison is intentional
|
|
if: needs.resolve-urls.result == 'success' && needs.resolve-urls.outputs.skipped == 'true'
|
|
steps:
|
|
- name: Get PR head SHA
|
|
id: pr-sha
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
|
with:
|
|
script: |
|
|
let sha = context.payload.pull_request?.head?.sha;
|
|
if (!sha) {
|
|
const prNumber = context.issue.number || Number(process.env.PR_NUMBER);
|
|
const { data: pr } = await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
});
|
|
sha = pr.head.sha;
|
|
}
|
|
core.setOutput('sha', sha);
|
|
|
|
- name: Create skipped visual review check run
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
env:
|
|
HEAD_SHA: ${{ steps.pr-sha.outputs.sha }}
|
|
SKIP_REASON: ${{ needs.resolve-urls.outputs.skip-reason }}
|
|
with:
|
|
script: |
|
|
const skipReason = process.env.SKIP_REASON || 'No previewable content changes detected';
|
|
const now = new Date().toISOString();
|
|
const { data: check } = await github.rest.checks.create({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: 'Visual Review',
|
|
head_sha: process.env.HEAD_SHA,
|
|
status: 'completed',
|
|
conclusion: 'skipped',
|
|
started_at: now,
|
|
completed_at: now,
|
|
output: {
|
|
title: 'Visual Review — Skipped',
|
|
summary: skipReason,
|
|
text: [
|
|
'## What This Means',
|
|
'',
|
|
'- No preview pages were identified for visual review.',
|
|
'- This is expected for PRs that only change non-content files.',
|
|
'- Copilot code review (diff-based) still runs independently.',
|
|
'',
|
|
'## If You Expected Visual Review',
|
|
'',
|
|
'Check that your PR includes changes to files in `content/` that map to published documentation pages.',
|
|
].join('\n'),
|
|
},
|
|
});
|
|
core.info(`Created skipped check run ${check.id}: ${skipReason}`);
|