From 0c45dad70d879059203f9a974b9f9e550c91a9a5 Mon Sep 17 00:00:00 2001 From: gabedos Date: Thu, 30 Jul 2026 11:27:52 -0400 Subject: [PATCH] qa chase --- .github/workflows/chase-for-qa-cards.yml | 34 ++ hack/release/chase_qa_cards.py | 113 ++++++ hack/release/github_jira_map.yaml | 63 ++++ hack/release/github_slack_map.yaml | 449 +++++++++++++++++++++++ 4 files changed, 659 insertions(+) create mode 100644 .github/workflows/chase-for-qa-cards.yml create mode 100644 hack/release/chase_qa_cards.py create mode 100644 hack/release/github_jira_map.yaml create mode 100644 hack/release/github_slack_map.yaml diff --git a/.github/workflows/chase-for-qa-cards.yml b/.github/workflows/chase-for-qa-cards.yml new file mode 100644 index 0000000000..2c60b809b6 --- /dev/null +++ b/.github/workflows/chase-for-qa-cards.yml @@ -0,0 +1,34 @@ +name: "Chase teams for QA cards" + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g. 1.9.0)' + required: true + type: string + +permissions: {} + +jobs: + chase_for_qa_cards: + runs-on: ubuntu-latest + environment: + name: main + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.12' + - name: Install dependencies + run: pip install atlassian-python-api slack_sdk pyyaml + - name: Chase for QA cards + env: + ATLASSIAN_USERNAME: ${{ secrets.ATLASSIAN_USERNAME }} + ATLASSIAN_PASSWORD: ${{ secrets.ATLASSIAN_PASSWORD }} + SLACK_DATADOG_AGENT_BOT_TOKEN: ${{ secrets.SLACK_DATADOG_AGENT_BOT_TOKEN }} + run: python hack/release/chase_qa_cards.py --version "${{ github.event.inputs.version }}" diff --git a/hack/release/chase_qa_cards.py b/hack/release/chase_qa_cards.py new file mode 100644 index 0000000000..158572475e --- /dev/null +++ b/hack/release/chase_qa_cards.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Chase teams for unfinished QA cards for a given operator release version. + +Usage: + python hack/release/chase_qa_cards.py --version 1.9.0 + python hack/release/chase_qa_cards.py --version 1.9.0 --dry-run + +Required environment variables: + ATLASSIAN_USERNAME Jira username (email) + ATLASSIAN_PASSWORD Jira API token + SLACK_DATADOG_AGENT_BOT_TOKEN Slack bot token (not required for --dry-run) +""" +import argparse +import os +import pathlib +import sys +from collections import defaultdict + +import yaml + +SCRIPT_DIR = pathlib.Path(__file__).parent +DEFAULT_SLACK_CHANNEL = "#container-platform" +DEFAULT_JIRA_PROJECT = "CONTP" + + +def _load_map(filename, default_placeholder, default_value, channel_type='notification'): + result = {} + with (SCRIPT_DIR / filename).open(encoding='utf-8') as f: + for key, value in yaml.safe_load(f).items(): + if isinstance(value, dict) and channel_type in value: + value = value[channel_type] + if isinstance(value, dict): + value = value.get('name', '') + result[key] = default_value if value == default_placeholder else value + return result + + +def list_not_closed_qa_cards(version): + from atlassian import Jira + + jira = Jira( + url="https://datadoghq.atlassian.net", + username=os.environ['ATLASSIAN_USERNAME'], + password=os.environ['ATLASSIAN_PASSWORD'], + cloud=True, + ) + jql = ( + f'labels in (ddqa) and labels not in (test_ignore) and labels in ({version}-qa) ' + 'and status not in ((Done, DONE, "Won\'t Fix", "WON\'T FIX", "In Progress", ' + '"Testing/Review", "In review", "✅ Done", "won\'t do", Duplicate, Closed, ' + '"NOT DOING", not-do, canceled, QA)) order by created desc' + ) + return jira.enhanced_jql(jql)['issues'] + + +def chase_for_qa_cards(version, dry_run=False): + cards = list_not_closed_qa_cards(version) + if not cards: + print(f"No QA cards to chase for {version} — all done!") + return + + grouped = defaultdict(list) + for card in cards: + grouped[card['fields']['project']['key']].append(card) + + slack_map = _load_map('github_slack_map.yaml', 'DEFAULT_SLACK_CHANNEL', DEFAULT_SLACK_CHANNEL) + jira_map = _load_map('github_jira_map.yaml', 'DEFAULT_JIRA_PROJECT', DEFAULT_JIRA_PROJECT) + jira_to_team = {project: team for team, project in jira_map.items()} + + print(f"Found {len(cards)} unfinished QA card(s) for {version}:") + + if not dry_run: + from slack_sdk import WebClient + client = WebClient(os.environ['SLACK_DATADOG_AGENT_BOT_TOKEN']) + + for project, project_cards in grouped.items(): + team = jira_to_team.get(project) + if not team: + print( + f" WARNING: no team found for Jira project {project}, " + f"skipping {[c['key'] for c in project_cards]}", + file=sys.stderr, + ) + continue + + channel = slack_map.get(team) + if not channel: + print(f" WARNING: no Slack channel for team {team}, skipping", file=sys.stderr) + continue + + card_links = ', '.join( + f"" + for c in project_cards + ) + message = ( + f"Hello :wave:\nCould you please update the QA cards {card_links} " + f"for the {version} release?\nThanks in advance" + ) + + print(f" -> {channel} ({team}): {[c['key'] for c in project_cards]}") + if not dry_run: + client.chat_postMessage(channel=channel, text=message) + + if dry_run: + print("\n(dry-run: no Slack messages sent)") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Chase teams for unfinished operator QA cards') + parser.add_argument('--version', required=True, help='Release version, e.g. 1.9.0') + parser.add_argument('--dry-run', action='store_true', help='Print planned messages without posting to Slack') + args = parser.parse_args() + chase_for_qa_cards(args.version, dry_run=args.dry_run) diff --git a/hack/release/github_jira_map.yaml b/hack/release/github_jira_map.yaml new file mode 100644 index 0000000000..d0cc5e0aed --- /dev/null +++ b/hack/release/github_jira_map.yaml @@ -0,0 +1,63 @@ +# This file contains a mapping of DataDog Github teams to JIRA projects. +# The DEFAULT_JIRA_PROJECT value is interpreted as "AGNTR". +# Note that keys must be quoted because the '@' symbol is reserved in YAML. +'@datadog/action-platform': ACTP +'@datadog/agent-platform': ACIX +'@datadog/documentation': DOCS +'@datadog/container-integrations': CONTINT +'@datadog/container-platform': CONTP +'@datadog/agent-onboarding': AGENTONB +'@datadog/container-autoscaling': CASCL +'@datadog/core-authn': AUTHN +'@datadog/agent-security': CWS +'@datadog/agent-apm': APMSP +'@datadog/network-device-monitoring-core': NDMC +'@datadog/ndm-integrations': NDINT +'@datadog/container-experiences': CXP +'@datadog/ecs-experiences': EXP +'@datadog/agent-metrics-logs': AMLII +'@datadog/agent-metric-pipelines': AGTMETRICS +'@datadog/agent-data-plane': DADP +'@datadog/agent-log-pipelines': AGNTLOG +'@datadog/agent-runtimes': AGENTRUN +'@datadog/agent-configuration': AGENTCFG +'@datadog/agent-supply-chain': SPPL +'@datadog/kubernetes-experiences': CAP +'@datadog/metrics-aggregation': AGGR +'@datadog/serverless': SVLS +'@datadog/serverless-azure-gcp': SVLS +'@datadog/remote-config': RC +'@datadog/fleet': FA +'@datadog/fleet-automation': FA +'@datadog/agent-community-eng': DEFAULT_JIRA_PROJECT +'@datadog/ebpf-platform': EBPF +'@datadog/cloud-network-monitoring': CNM +'@datadog/network-path': NETPATH +'@datadog/universal-service-monitoring': USMON +'@datadog/windows-products': WINA +'@datadog/opentelemetry': OTAGENT +'@datadog/opentelemetry-agent': OTAGENT +'@datadog/sdlc-security': SINT +'@datadog/sensitive-data-scanner': SDS +'@datadog/single-machine-performance': SMP +'@datadog/agent-integrations': AI +'@datadog/debugger': DEBUG +'@datadog/debugger-go': DEBUG +'@datadog/database-monitoring': DBMON +'@datadog/data-observability': DO +'@datadog/data-streams-monitoring': DSMON +'@datadog/agent-cspm': SEC +'@datadog/apm-release-platform': APMLP +'@datadog/apm-trace-storage': TDH +'@datadog/asm-go': APPSEC +'@datadog/agent-delivery': BARX +'@datadog/agent-build': ABLD +'@datadog/agent-devx': ACIX +'@datadog/injection-platform': INPLAT +'@datadog/agent-processing-and-routing': APR +'@datadog/agent-discovery': DSCVR +'@datadog/synthetics-executing': SYNORG +'@datadog/fleet-remediation': FLREM +'@datadog/profiling-full-host': 'PROF' +'@datadog/q-branch': DEFAULT_JIRA_PROJECT +'@datadog/gpu-monitoring-agent': EBPF diff --git a/hack/release/github_slack_map.yaml b/hack/release/github_slack_map.yaml new file mode 100644 index 0000000000..a407c78366 --- /dev/null +++ b/hack/release/github_slack_map.yaml @@ -0,0 +1,449 @@ +# This file contains a mapping of DataDog Github teams to slack channels. +# Each team has a 'notification' channel (for CI/pipeline alerts) and a 'review' channel (for PR reviews). +# The DEFAULT_SLACK_CHANNEL value is interpreted as '#agent-devx-ops'. +# Note that the values must be quoted if they contain a '#' symbol, because +# YAML interprets that as the beginning of a comment. Keys must also be quoted +# because the '@' symbol is reserved in YAML. +'@datadog/action-platform': + notification: + name: '#action-platform' + id: 'C037JEVR8S3' + review: + name: '#action-platform-reviews' + id: 'C0AG2UCHUKZ' +'@datadog/agent-community-eng': + notification: + name: '#datadog-agent-pipelines' + id: 'CR5TV8QBY' + review: + name: '#datadog-agent-pipelines' + id: 'CR5TV8QBY' +'@datadog/agent-apm': + notification: + name: '#apm-agent' + id: 'CK4KK761W' + review: + name: '#apm-agent' + id: 'CK4KK761W' +'@datadog/agent-build': + notification: + name: '#agent-build-ops' + id: 'C08T0U0079Q' + review: + name: '#agent-build-reviews' + id: 'C08SK57TL1M' +'@datadog/agent-configuration': + notification: + name: '#agent-configuration-ops' + id: 'C08A85VGC8P' + review: + # DO NOT FILL IN - team opted out of PR review notifications, see: + # https://github.com/DataDog/datadog-agent/pull/51716#issuecomment-4610991841 + name: '' + id: '' +'@datadog/agent-cspm': + notification: + name: '#security-and-compliance-agent-ops' + id: 'C031KSGGNBW' + review: + name: '#security-and-compliance-agent' + id: 'CTNVD37T3' +'@datadog/agent-data-plane': + notification: + name: '#agent-data-plane' + id: 'C070PQKLLP5' + review: + name: '#agent-data-plane' + id: 'C070PQKLLP5' +'@datadog/agent-delivery': + notification: + name: '#agent-delivery-ops' + id: 'C079U54FYSU' + review: + name: '#agent-delivery-reviews' + id: 'C06PQ6KD5BK' +'@datadog/agent-devx': + notification: + name: '#agent-devx-ops' + id: 'C0795C1C2AE' + review: + name: '#agent-devx-reviews' + id: 'C06P2KX149M' +'@datadog/agent-discovery': + notification: + name: '#agent-discovery' + id: 'C089ER2M7MZ' + review: + name: '#agent-discovery' + id: 'C089ER2M7MZ' +'@datadog/fleet-remediation': + notification: + name: '#fleet-remediation' + id: 'C08U2C0GCMV' + review: + name: '#fleet-remediation' + id: 'C08U2C0GCMV' +'@datadog/agent-integrations': + notification: + name: '#agent-integrations' + id: 'C9WBCH0AF' + review: + name: '#agent-integrations-reviews' + id: 'C06E366NH2B' +'@datadog/agent-log-pipelines': + notification: + name: '#agent-log-pipelines' + id: 'C089UJ6CUAW' + review: + name: '#agent-log-pipelines' + id: 'C089UJ6CUAW' +'@datadog/agent-metric-pipelines': + notification: + name: '#agent-metric-pipelines' + id: 'C08A7KEN7LL' + review: + name: '#agent-metric-pipelines' + id: 'C08A7KEN7LL' +'@datadog/agent-metrics-logs': + notification: + name: '#agent-metrics-logs' + id: 'C03EC7JKSMV' + review: + name: '#agent-metrics-logs' + id: 'C03EC7JKSMV' +'@datadog/agent-onboarding': + notification: + name: '#agent-onboarding-ops' + id: 'C03THNXNJSZ' + review: + name: '#agent-onboarding-ops' + id: 'C03THNXNJSZ' +'@datadog/agent-platform': + notification: + name: 'DEFAULT_SLACK_CHANNEL' + id: 'C0795C1C2AE' + review: + name: 'DEFAULT_SLACK_CHANNEL' + id: 'C0795C1C2AE' +'@datadog/agent-processing-and-routing': + notification: + name: '#agent-processing-and-routing' + id: 'C06DYV5HN3Y' + review: + name: '#agent-processing-and-routing' + id: 'C06DYV5HN3Y' +'@datadog/agent-runtimes': + notification: + name: '#agent-runtimes-ops' + id: 'C08AG3H5RN2' + review: + name: '#agent-runtimes-reviews' + id: 'C0ABM4KFQ6Q' +'@datadog/agent-security': + notification: + name: '#security-and-compliance-agent-ops' + id: 'C031KSGGNBW' + review: + name: '#security-and-compliance-agent' + id: 'CTNVD37T3' +'@datadog/apm-ecosystems-performance': + notification: + name: '#apm-benchmarking-platform' + id: 'C045F97HYJD' + review: + name: '#apm-benchmarking-platform' + id: 'C045F97HYJD' +'@datadog/apm-onboarding': + notification: + name: '#apm-onboarding' + id: 'CLWN64GAW' + review: + name: '#apm-onboarding' + id: 'CLWN64GAW' +'@datadog/apm-release-platform': + notification: + name: '#apm-release-platform' + id: 'C07AFGS1N81' + review: + name: '#apm-release-platform' + id: 'C07AFGS1N81' +'@datadog/apm-trace-storage': + notification: + name: '#apm-trace-storage' + id: 'C02FKLS4ETW' + review: + name: '#apm-trace-storage' + id: 'C02FKLS4ETW' +'@datadog/asm-go': + notification: + name: '#k9-library-go' + id: 'C02HVGRLQ8Z' + review: + name: '#k9-library-go' + id: 'C02HVGRLQ8Z' +'@datadog/cloud-network-monitoring': + notification: + name: '#cnm-agent' + id: 'C07FNUP1M3N' + review: + name: '#cnm-agent' + id: 'C07FNUP1M3N' +'@datadog/container-autoscaling': + notification: + name: '#container-autoscaling' + id: 'C06D0H262G4' + review: + name: '#container-autoscaling' + id: 'C06D0H262G4' +'@datadog/container-experiences': + notification: + name: '#process-agent-ops' + id: 'C0493DCUR6C' + review: + name: '#container-experiences' + id: 'C05404GL3R8' +'@datadog/container-integrations': + notification: + name: '#container-integrations' + id: 'C4TQDFK1P' + review: + name: '#container-integrations' + id: 'C4TQDFK1P' +'@datadog/container-platform': + notification: + name: '#container-platform' + id: 'C0702JHC7G8' + review: + name: '#container-platform' + id: 'C0702JHC7G8' +'@datadog/core-authn': + notification: + name: '#aaa-authn' + id: 'C02S3STU39N' + review: + name: '#aaa-authn' + id: 'C02S3STU39N' +'@datadog/data-observability': + notification: + name: '#data-observability-ops' + id: 'C08KJQRNRJ6' + review: + name: '#data-observability-dev' + id: 'C05VB5B6U1H' +'@datadog/data-streams-monitoring': + notification: + name: '#data-streams-monitoring' + id: 'C02FFNF9VN0' + review: + name: '#data-streams-monitoring' + id: 'C02FFNF9VN0' +'@datadog/database-monitoring': + notification: + name: '#database-monitoring' + id: 'C011QG8MJBA' + review: + name: '#database-monitoring' + id: 'C011QG8MJBA' +'@datadog/dd-agent-mcp': + notification: + name: '#dd-agent-mcp' + id: 'C0A9L3290GZ' + review: + name: '#dd-agent-mcp' + id: 'C0A9L3290GZ' +'@datadog/debugger': + notification: + name: '#debugger-ops-prod' + id: 'C01TX4LJGTB' + review: + name: '' + id: '' +'@datadog/debugger-go': + notification: + name: '#debugger-go' + id: 'C09DBHFJE4E' + review: + name: '#debugger-go' + id: 'C09DBHFJE4E' +'@datadog/documentation': + notification: + name: 'DEFAULT_SLACK_CHANNEL' + id: 'C0795C1C2AE' + review: + name: '' + id: '' +'@datadog/ebpf-platform': + notification: + name: '#ebpf-platform-ops' + id: 'C04NAC0JVMM' + review: + name: '#ebpf-platform' + id: 'C0424HA1SJK' +'@datadog/ecs-experiences': + notification: + name: '#ecs-experiences' + id: 'C09FX078WH2' + review: + name: '#ecs-experiences' + id: 'C09FX078WH2' +'@datadog/fleet': + notification: + name: '#fleet-automation-monitoring' + id: 'C06TZ0APY6Q' + review: + name: '#fleet-automation' + id: 'C05PUSNU4H5' +'@datadog/fleet-automation': + notification: + name: '#fleet-automation-monitoring' + id: 'C06TZ0APY6Q' + review: + name: '#fleet-automation' + id: 'C05PUSNU4H5' +'@datadog/gpu-monitoring-agent': + notification: + name: '#gpu-monitoring-agent-ops' + id: 'C0APTNVCC04' + review: + name: '#gpu-monitoring-agent-reviews' + id: 'C0B7VF7LKGT' +'@datadog/injection-platform': + notification: + name: '#injection-platform-alerts' + id: 'C09LN4BCCP7' + review: + name: '#injection-platform' + id: 'C06UEUW9A75' +'@datadog/kubernetes-experiences': + notification: + name: '#kubernetes-experiences' + id: 'CSY06FKSP' + review: + name: '#kubernetes-experiences' + id: 'CSY06FKSP' +'@datadog/metrics-aggregation': + notification: + name: '#metrics-aggregation' + id: 'C573SFWSJ' + review: + name: '#metrics-aggregation' + id: 'C573SFWSJ' +'@datadog/ndm-integrations': + notification: + name: '#ndm-integrations' + id: 'C07S0UH4R0C' + review: + name: '#ndm-integrations' + id: 'C07S0UH4R0C' +'@datadog/network-device-monitoring-core': + notification: + name: '#ndm-core' + id: 'C07RHJTEFRD' + review: + name: '#ndm-core' + id: 'C07RHJTEFRD' +'@datadog/network-path': + notification: + name: '#network-path-dev' + id: 'C06GLJ7SHM3' + review: + name: '#network-path-dev' + id: 'C06GLJ7SHM3' +'@datadog/opentelemetry': + notification: + name: '#opentelemetry-ops' + id: 'C045DGB1V39' + review: + name: '#opentelemetry-ops' + id: 'C045DGB1V39' +'@datadog/opentelemetry-agent': + notification: + name: '#opentelemetry-agent-ops' + id: 'C08R19TR3FS' + review: + name: '#opentelemetry-agent' + id: 'C086Z7E2A0Y' +'@datadog/profiling-full-host': + notification: + name: '#profiling-full-host-project' + id: 'C06F2EP2SDA' + review: + name: '#profiling-full-host-project' + id: 'C06F2EP2SDA' +'@datadog/remote-config': + notification: + name: '#remote-config-platform' + id: 'C01HVED6Q1M' + review: + name: '#remote-config-platform' + id: 'C01HVED6Q1M' +'@datadog/sdlc-security': + notification: + name: '#sdlc-security' + id: 'C027P1CK07N' + review: + name: '#sdlc-security' + id: 'C027P1CK07N' +'@datadog/sensitive-data-scanner': + notification: + name: '#sensitive-data-scanner' + id: 'C035A167T0S' + review: + name: '#sensitive-data-scanner' + id: 'C035A167T0S' +'@datadog/serverless': + notification: + name: '#serverless-agent' + id: 'C017ES2MY05' + review: + name: '#serverless-agent' + id: 'C017ES2MY05' +'@datadog/serverless-azure-gcp': + notification: + name: '#serverless-azure-gcp-team' + id: 'C06P8R33PD3' + review: + name: '#serverless-azure-gcp-team' + id: 'C06P8R33PD3' +'@datadog/single-machine-performance': + notification: + name: '#single-machine-performance' + id: 'C03LY8MQ73R' + review: + name: '#single-machine-performance' + id: 'C03LY8MQ73R' +'@datadog/synthetics-executing': + notification: + name: '#synthetics' + id: 'C8E85JEGH' + review: + name: '#synthetics' + id: 'C8E85JEGH' +'@datadog/telemetry-onboarding': + notification: + name: '#telemetry-onboarding' + id: 'C0ACU5FGE3H' + review: + name: '#telemetry-onboarding' + id: 'C0ACU5FGE3H' +'@datadog/universal-service-monitoring': + notification: + name: '#universal-service-monitoring' + id: 'C01R6Q0HQDD' + review: + name: '#universal-service-monitoring' + id: 'C01R6Q0HQDD' +'@datadog/q-branch': + notification: + name: '#agent-q-branch' + id: 'C09UDTU0H18' + review: + name: '#agent-q-branch' + id: 'C09UDTU0H18' +'@datadog/windows-products': + notification: + name: '#windows-products-ops' + id: 'C097S0V4KHR' + review: + name: '#windows-products-reviews' + id: 'C0983QTJJJ0'