Skip to content

Report triage

Report triage #63

Workflow file for this run

# .github/workflows/release-triage.yml
#
# Manual triage for freeze week / release promotions.
# Actions tab -> "Report triage" -> Run workflow -> type the release tag.
# Creates ONE Taiga user story tagged "release-<tag>" containing one task
# per failure cluster. Re-running with the same tag adds only NEW clusters
# to the SAME story (no duplicates) thanks to the per-release state file.
# Tick close_only to sweep and close resolved tasks instead, without filing anything new.
name: Report triage
on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag (e.g. 2.17) — story will be tagged release-<tag>'
required: true
report_run_id:
description: 'Override: specific run id to triage (default: last scheduled daily run on main)'
required: false
group_by:
description: 'Task granularity inside the release story. folder: snapshot failures bundled per immediate parent folder (claimable per-folder for splitting snapshot-update work); functional bugs stay one-per-cluster'
required: false
default: 'cluster'
type: choice
options:
- cluster
- file
- folder
epic_ref:
description: 'Optional: Taiga epic ref (number only, e.g. 118) to link the story under'
required: false
reset_state:
description: 'Reset this release tag: forget all previous triage state (delete the old story in Taiga yourself first)'
required: false
default: false
type: boolean
close_only:
description: 'Close-only sweep: close tasks whose error is no longer failing — files nothing new. Do not combine with reset_state.'
required: false
default: false
type: boolean
permissions:
contents: read
actions: read # required for gh run list
jobs:
triage:
runs-on: ubuntu-latest
environment: PRE # grants access to MATTERMOST_WEBHOOK_DAILY_REPORT (environment secret)
steps:
- name: Reject reset_state + close_only together
if: ${{ inputs.reset_state == true && inputs.close_only == true }}
run: |
echo "reset_state wipes triage state right before close_only would need it to find what's resolved — pick one." >&2
exit 1
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Resolve report to triage
id: report
run: |
if [ -n "${{ inputs.report_run_id }}" ]; then
RUN_ID="${{ inputs.report_run_id }}"
echo "Using manually provided run: $RUN_ID"
else
# Last finished *scheduled* daily run on main (completed, not success:
# the test step fails the workflow on failing nights by design)
RUN_ID=$(gh run list \
--workflow "$DAILY_WORKFLOW" \
--branch main \
--event schedule \
--status completed \
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "No completed scheduled daily run found on main"; exit 1
fi
echo "Using last scheduled daily run on main: $RUN_ID"
fi
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
aws s3 cp "s3://kaleidos-qa-reports/run-$RUN_ID/results.json" results.json
aws s3 cp "s3://kaleidos-qa-reports/run-$RUN_ID-enterprise/results.json" results-enterprise.json 2>/dev/null \
&& echo "Found enterprise results for this run — will be triaged alongside the standard suite." \
|| echo "No enterprise results for this run (older run, or the enterprise job didn't run/upload) — triaging standard suite only."
env:
GH_TOKEN: ${{ github.token }}
DAILY_WORKFLOW: playwright_pre_daily.yml # "Daily Penpot Regression Tests on PRE"
- name: Fetch app version for this run
id: appver
run: |
VERSION=$(aws s3 cp "s3://kaleidos-qa-reports/run-${{ steps.report.outputs.run_id }}/app-version.txt" - 2>/dev/null || echo "")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "App version for this run: ${VERSION:-<not recorded>}"
- name: Reset per-release triage state
if: ${{ inputs.reset_state == true }}
run: |
aws s3 rm "s3://kaleidos-qa-reports/triage/state-release-${{ inputs.release_tag }}.json" || echo "no state to delete"
echo "State for release ${{ inputs.release_tag }} reset — all failures will be treated as new."
- name: Pull per-release triage state
run: |
mkdir -p .triage
aws s3 cp "s3://kaleidos-qa-reports/triage/state-release-${{ inputs.release_tag }}.json" \
.triage/state.json || echo "first triage for this release"
- name: Run report triage
run: |
CLOSE_ONLY_FLAG=""
if [ "${{ inputs.close_only }}" = "true" ]; then
CLOSE_ONLY_FLAG="--close-only"
echo "Close-only sweep: closing resolved tasks, filing nothing new."
fi
ENTERPRISE_RESULTS_FLAG=""
if [ -f results-enterprise.json ]; then
ENTERPRISE_RESULTS_FLAG="--results results-enterprise.json"
fi
npx tsx scripts/triage.ts \
--results results.json \
$ENTERPRISE_RESULTS_FLAG \
--state .triage/state.json \
--release "${{ inputs.release_tag }}" \
--group-by "${{ inputs.group_by || 'cluster' }}" \
--epic-ref "${{ inputs.epic_ref }}" \
--app-version "${{ steps.appver.outputs.version }}" \
--run-id "${{ steps.report.outputs.run_id }}" \
--report-url "https://kaleidos-qa-reports.s3.eu-west-1.amazonaws.com/run-${{ steps.report.outputs.run_id }}/index.html" \
$CLOSE_ONLY_FLAG
env:
GITHUB_TOKEN: ${{ github.token }} # raises GitHub API rate limit for the app-repo changelog fetch
# APP_REPO: penpot/penpot # default; override if the app repo moves
TAIGA_CLOSE_ASSIGNEE: qa.integrations.bot # resolved tasks are assigned to this user when auto-closed
# TAIGA_CLOSED_STATUS: Closed # default; override if the project's closed task status is named differently
TAIGA_URL: ${{ secrets.TAIGA_URL }} # https://api.taiga.io on Taiga cloud
TAIGA_PUBLIC_URL: ${{ vars.TAIGA_PUBLIC_URL }} # https://tree.taiga.io — used for links in the digest
TAIGA_USERNAME: ${{ secrets.TAIGA_USERNAME }}
TAIGA_PASSWORD: ${{ secrets.TAIGA_PASSWORD }}
TAIGA_PROJECT: ${{ vars.TAIGA_PROJECT }}
TAIGA_TAGS: 'needs-triage' # release-<tag> is added automatically
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_DAILY_REPORT }} # PRE environment secret -> Autotests Reports channel
# DRY_RUN: "1"
- name: Push per-release triage state
if: always()
run: |
if [ -f .triage/state.json ]; then
aws s3 cp .triage/state.json \
"s3://kaleidos-qa-reports/triage/state-release-${{ inputs.release_tag }}.json"
else
echo "No state file to push (dry run leaves no trace)"
fi
- name: Digest to job summary
if: always()
run: cat .triage/digest.md >> "$GITHUB_STEP_SUMMARY"