From 18f2678699f0210d970ec107214830846fabf984 Mon Sep 17 00:00:00 2001 From: ShodiBoy1 Date: Mon, 29 Jun 2026 23:02:43 +0200 Subject: [PATCH 1/6] CI status watch and external discussion observer workflows --- .github/workflows/ci-status-watch.yml | 85 ++++++++++++++++ .github/workflows/issue-pr-observer.yml | 123 ++++++++++++++++-------- 2 files changed, 169 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/ci-status-watch.yml diff --git a/.github/workflows/ci-status-watch.yml b/.github/workflows/ci-status-watch.yml new file mode 100644 index 000000000..1cef73d5d --- /dev/null +++ b/.github/workflows/ci-status-watch.yml @@ -0,0 +1,85 @@ +name: CI status watch + +# Posts a weekday-morning digest to a Teams channel listing the workflows that are +# currently red on the default branch, so a broken CI is noticed in the Daily and the +# team can react (e.g. open an issue). Reports current state, not history, so a workflow +# that stays red shows up each morning until it is green again. + +on: + workflow_dispatch: + schedule: + - cron: '0 7 * * 1-5' # 07:00 UTC, Mon-Fri (= 09:00 CEST / 08:00 CET) + +permissions: + actions: read + contents: read + +jobs: + watch: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WEBHOOK: ${{ secrets.TEAMS_WEBHOOK_URL }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.event.repository.default_branch }} + SELF: .github/workflows/ci-status-watch.yml + steps: + - name: Collect red workflows and notify Teams + run: | + set -euo pipefail + + # For every active workflow, look at its latest finished run on the default + # branch and keep the ones whose conclusion counts as a red badge. + RED=$(gh api "repos/$REPO/actions/workflows" \ + --jq '.workflows[] | select(.state=="active") | [.name, .path] | @tsv' \ + | while IFS=$'\t' read -r NAME WF_PATH; do + [ "$WF_PATH" = "$SELF" ] && continue + DATA=$(gh run list --repo "$REPO" --workflow "$WF_PATH" --branch "$BRANCH" \ + --status completed --limit 1 --json conclusion,url \ + --jq '.[0] // empty | "\(.conclusion)\t\(.url)"' || true) + [ -z "$DATA" ] && continue + CONCL=${DATA%%$'\t'*} + URL=${DATA#*$'\t'} + if [ "$CONCL" = "failure" ] || [ "$CONCL" = "timed_out" ] || [ "$CONCL" = "startup_failure" ]; then + jq -n --arg n "$NAME" --arg u "$URL" --arg c "$CONCL" \ + '{name:$n, url:$u, conclusion:$c}' + fi + done | jq -s '.') + + COUNT=$(echo "$RED" | jq 'length') + if [ "$COUNT" -eq 0 ]; then + echo "All workflows green on $BRANCH. Skipping Teams notification." + exit 0 + fi + echo "$COUNT red workflow(s) on $BRANCH." + + if [ -z "${WEBHOOK:-}" ]; then + echo "No Teams webhook set, skipping notification."; exit 0 + fi + + # Bare Adaptive Card, same shape as the issue/PR observer. + CARD=$(echo "$RED" | jq --argjson count "$COUNT" --arg branch "$BRANCH" '{ + type: "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.4", + body: ( + [ + { type: "TextBlock", size: "Large", weight: "Bolder", wrap: true, color: "attention", + text: "🔴 Rote Workflows" }, + { type: "TextBlock", spacing: "None", isSubtle: true, wrap: true, + text: ("Branch `" + $branch + "` · " + (if $count == 1 then "1 Workflow rot" else "\($count) Workflows rot" end)) } + ] + + [ .[] | { + type: "Container", + separator: true, + spacing: "Medium", + items: [ + { type: "TextBlock", weight: "Bolder", wrap: true, text: "🔴 \(.name)" }, + { type: "TextBlock", spacing: "None", isSubtle: true, size: "Small", wrap: true, + text: "\(.conclusion) · [Run-Log öffnen](\(.url))" } + ] + } ] + ) + }') + + curl -sS --fail -X POST -H "Content-Type: application/json" -d "$CARD" "$WEBHOOK" diff --git a/.github/workflows/issue-pr-observer.yml b/.github/workflows/issue-pr-observer.yml index ab540a856..f132fc6fa 100644 --- a/.github/workflows/issue-pr-observer.yml +++ b/.github/workflows/issue-pr-observer.yml @@ -1,25 +1,28 @@ -name: External Issue & PR Observer +name: External Issue, PR & Discussion Observer -# Posts a weekday-morning digest of new EXTERNAL issues/PRs (last 24h) to a Teams -# channel so incoming reports from outside the team are noticed quickly and don't slip. -# "External" = author_association is not MEMBER, OWNER or COLLABORATOR. +# Posts a weekday-morning digest of new EXTERNAL issues, PRs and discussions (last 24h) +# to a Teams channel so incoming activity from outside the team is noticed quickly and +# doesn't slip. "External" = author association is not MEMBER, OWNER or COLLABORATOR. +# Issues/PRs come from the REST search API, discussions from GraphQL (no REST equivalent). +# Each source falls back to empty on error, so one failing API still posts the rest. # Requires the repository secret TEAMS_WEBHOOK_URL (a Teams "Workflows" incoming webhook). on: - workflow_dispatch: + workflow_dispatch: schedule: - cron: '0 7 * * 1-5' # 07:00 UTC, Mon-Fri (= 09:00 CEST summer / 08:00 CET winter) permissions: issues: read pull-requests: read + discussions: read contents: read jobs: observe: runs-on: ubuntu-latest steps: - - name: Scan for new external issues & PRs and notify Teams + - name: Scan for new external issues, PRs & discussions and notify Teams env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} WEBHOOK: ${{ secrets.TEAMS_WEBHOOK_URL }} @@ -33,9 +36,9 @@ jobs: fi echo "Scanning $REPO for items created since $SINCE" - # Search returns both issues and PRs; keep only external authors (not on the team) - # and emit a structured object per item so the card can lay them out nicely. - ITEMS_JSON=$(gh api -X GET search/issues \ + # Issues + PRs via REST search; keep only external authors. On error fall back to + # an empty list so a discussions failure (below) can still be reported on its own. + ISSUE_ITEMS=$(gh api -X GET search/issues \ -f q="repo:$REPO created:>=$SINCE" \ --jq '[.items[] | select(.author_association != "MEMBER" @@ -48,40 +51,82 @@ jobs: title: .title, url: .html_url, user: .user.login - }]') + }]') \ + || { echo "WARN: issue/PR fetch failed, continuing without them."; ISSUE_ITEMS='[]'; } - COUNT=$(echo "$ITEMS_JSON" | jq 'length') + # Discussions have no REST search, so use GraphQL: take the newest, then filter by + # the same time window and external-author rule. 50 is plenty for a daily window. + OWNER=${REPO%/*} + NAME=${REPO#*/} + DISC_ITEMS=$(gh api graphql \ + -f owner="$OWNER" -f name="$NAME" \ + -f query=' + query($owner:String!, $name:String!) { + repository(owner:$owner, name:$name) { + discussions(first:50, orderBy:{field:CREATED_AT, direction:DESC}) { + nodes { number title url createdAt authorAssociation author { login } } + } + } + }' \ + --jq '.data.repository.discussions.nodes' \ + | jq --arg since "$SINCE" '[ .[] + | select(.createdAt >= $since) + | select(.authorAssociation != "MEMBER" + and .authorAssociation != "OWNER" + and .authorAssociation != "COLLABORATOR") + | { + kind: "Discussion", + icon: "💬", + number: .number, + title: .title, + url: .url, + user: (.author.login // "unknown") + } ]') \ + || { echo "WARN: discussion fetch failed, continuing without them."; DISC_ITEMS='[]'; } + + IP_COUNT=$(echo "$ISSUE_ITEMS" | jq 'length') + D_COUNT=$(echo "$DISC_ITEMS" | jq 'length') + COUNT=$((IP_COUNT + D_COUNT)) if [ "$COUNT" -eq 0 ]; then - echo "No new external issues or PRs. Skipping Teams-notification." + echo "No new external issues, PRs or discussions. Skipping Teams-notification." exit 0 fi - echo "$COUNT new external issue(s)/PR(s) found." + echo "$COUNT new external item(s): $IP_COUNT issue(s)/PR(s), $D_COUNT discussion(s)." - # Teams' webhook wants a bare Adaptive Card: top-level type must be - # "AdaptiveCard", not wrapped in a message/attachments envelope. - CARD=$(echo "$ITEMS_JSON" | jq --argjson count "$COUNT" '{ - type: "AdaptiveCard", - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - version: "1.4", - body: ( - [ - { type: "TextBlock", size: "Large", weight: "Bolder", wrap: true, - text: "🆕 Neue externe Issues & Pull Requests" }, - { type: "TextBlock", spacing: "None", isSubtle: true, wrap: true, - text: ("Letzter Zeitraum · " + (if $count == 1 then "1 neuer Eintrag" else "\($count) neue Einträge" end)) } - ] - + [ .[] | { - type: "Container", - separator: true, - spacing: "Medium", - items: [ - { type: "TextBlock", weight: "Bolder", wrap: true, - text: "\(.icon) #\(.number) · \(.title)" }, - { type: "TextBlock", spacing: "None", isSubtle: true, size: "Small", wrap: true, - text: "\(.kind) · von @\(.user) · [Öffnen](\(.url))" } - ] - } ] - ) - }') + # Bare Adaptive Card with one group per source; a group is rendered only if it has + # items, so issues/PRs and discussions stay separated and empty sections are hidden. + CARD=$(jq -n \ + --argjson ip "$ISSUE_ITEMS" \ + --argjson disc "$DISC_ITEMS" \ + --argjson count "$COUNT" ' + def section(title; items): + if (items | length) > 0 then + [ { type: "TextBlock", size: "Medium", weight: "Bolder", wrap: true, + spacing: "Large", text: title } ] + + [ items[] | { + type: "Container", separator: true, spacing: "Medium", + items: [ + { type: "TextBlock", weight: "Bolder", wrap: true, + text: "\(.icon) #\(.number) · \(.title)" }, + { type: "TextBlock", spacing: "None", isSubtle: true, size: "Small", wrap: true, + text: "\(.kind) · von @\(.user) · [Öffnen](\(.url))" } + ] + } ] + else [] end; + { + type: "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.4", + body: ( + [ + { type: "TextBlock", size: "Large", weight: "Bolder", wrap: true, + text: "🆕 Neue externe Aktivität" }, + { type: "TextBlock", spacing: "None", isSubtle: true, wrap: true, + text: ("Letzter Zeitraum · " + (if $count == 1 then "1 neuer Eintrag" else "\($count) neue Einträge" end)) } + ] + + section("📋 Issues & Pull Requests"; $ip) + + section("💬 Discussions"; $disc) + ) + }') curl -sS --fail -X POST -H "Content-Type: application/json" -d "$CARD" "$WEBHOOK" From d27c73ac031f2b686aecfa9ed6b5c187d72566f9 Mon Sep 17 00:00:00 2001 From: ShodiBoy1 Date: Mon, 29 Jun 2026 23:03:09 +0200 Subject: [PATCH 2/6] CI status watch and external discussion observer workflows --- .github/workflows/ci-status-watch.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci-status-watch.yml b/.github/workflows/ci-status-watch.yml index 1cef73d5d..a16623778 100644 --- a/.github/workflows/ci-status-watch.yml +++ b/.github/workflows/ci-status-watch.yml @@ -12,7 +12,6 @@ on: permissions: actions: read - contents: read jobs: watch: From 60bfafb9c6bd39d27c51370dced76b8cd4ae5e3b Mon Sep 17 00:00:00 2001 From: ShodiBoy1 Date: Tue, 30 Jun 2026 09:03:49 +0200 Subject: [PATCH 3/6] CI status watch --- .github/workflows/ci-status-watch.yml | 57 ++++++++++++++++----------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci-status-watch.yml b/.github/workflows/ci-status-watch.yml index a16623778..09c870f19 100644 --- a/.github/workflows/ci-status-watch.yml +++ b/.github/workflows/ci-status-watch.yml @@ -1,9 +1,10 @@ name: CI status watch -# Posts a weekday-morning digest to a Teams channel listing the workflows that are -# currently red on the default branch, so a broken CI is noticed in the Daily and the -# team can react (e.g. open an issue). Reports current state, not history, so a workflow -# that stays red shows up each morning until it is green again. +# Posts a Teams message ONLY when a workflow on the default branch turns red +# (green -> red). For each workflow it compares the two latest runs with gh; a change +# is reported only if the newest run finished within the look-back window, so a workflow +# that stays red is not repeated every day. +# Requires the repository secret TEAMS_WEBHOOK_URL. on: workflow_dispatch: @@ -23,50 +24,60 @@ jobs: BRANCH: ${{ github.event.repository.default_branch }} SELF: .github/workflows/ci-status-watch.yml steps: - - name: Collect red workflows and notify Teams + - name: Detect newly red workflows and notify Teams run: | set -euo pipefail - # For every active workflow, look at its latest finished run on the default - # branch and keep the ones whose conclusion counts as a red badge. + # Monday catches up the weekend (Fri-Sun); other weekdays look back 24h. + if [ "$(date -u +%u)" -eq 1 ]; then + SINCE=$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ) + else + SINCE=$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ) + fi + echo "Checking $REPO for workflows that turned red since $SINCE" + + is_red() { [ "$1" = "failure" ] || [ "$1" = "timed_out" ] || [ "$1" = "startup_failure" ]; } + + # For each active workflow, compare its two latest runs and report it only if it + # just turned red and that run finished inside the window (so a lasting red is + # reported just once, not every day). RED=$(gh api "repos/$REPO/actions/workflows" \ --jq '.workflows[] | select(.state=="active") | [.name, .path] | @tsv' \ | while IFS=$'\t' read -r NAME WF_PATH; do [ "$WF_PATH" = "$SELF" ] && continue - DATA=$(gh run list --repo "$REPO" --workflow "$WF_PATH" --branch "$BRANCH" \ - --status completed --limit 1 --json conclusion,url \ - --jq '.[0] // empty | "\(.conclusion)\t\(.url)"' || true) - [ -z "$DATA" ] && continue - CONCL=${DATA%%$'\t'*} - URL=${DATA#*$'\t'} - if [ "$CONCL" = "failure" ] || [ "$CONCL" = "timed_out" ] || [ "$CONCL" = "startup_failure" ]; then - jq -n --arg n "$NAME" --arg u "$URL" --arg c "$CONCL" \ - '{name:$n, url:$u, conclusion:$c}' + ROW=$(gh run list --repo "$REPO" --workflow "$WF_PATH" --branch "$BRANCH" \ + --status completed --limit 2 --json conclusion,url,updatedAt \ + --jq '[(.[0].conclusion // ""), (.[0].updatedAt // ""), (.[0].url // ""), (.[1].conclusion // "")] | @tsv' || true) + [ -z "$ROW" ] && continue + IFS=$'\t' read -r CUR CURTIME URL PREV <<< "$ROW" + [ -z "$CUR" ] && continue + [[ "$CURTIME" > "$SINCE" ]] || continue + if is_red "$CUR" && { [ "$PREV" = "success" ] || [ -z "$PREV" ]; }; then + jq -n --arg n "$NAME" --arg u "$URL" '{name:$n, url:$u}' fi done | jq -s '.') COUNT=$(echo "$RED" | jq 'length') if [ "$COUNT" -eq 0 ]; then - echo "All workflows green on $BRANCH. Skipping Teams notification." + echo "No workflow turned red. Skipping Teams notification." exit 0 fi - echo "$COUNT red workflow(s) on $BRANCH." + echo "$COUNT workflow(s) turned red." if [ -z "${WEBHOOK:-}" ]; then echo "No Teams webhook set, skipping notification."; exit 0 fi - # Bare Adaptive Card, same shape as the issue/PR observer. - CARD=$(echo "$RED" | jq --argjson count "$COUNT" --arg branch "$BRANCH" '{ + CARD=$(echo "$RED" | jq --argjson count "$COUNT" '{ type: "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", version: "1.4", body: ( [ { type: "TextBlock", size: "Large", weight: "Bolder", wrap: true, color: "attention", - text: "🔴 Rote Workflows" }, + text: "🔴 Workflow rot geworden" }, { type: "TextBlock", spacing: "None", isSubtle: true, wrap: true, - text: ("Branch `" + $branch + "` · " + (if $count == 1 then "1 Workflow rot" else "\($count) Workflows rot" end)) } + text: (if $count == 1 then "1 Workflow · grün → rot" else "\($count) Workflows · grün → rot" end) } ] + [ .[] | { type: "Container", @@ -75,7 +86,7 @@ jobs: items: [ { type: "TextBlock", weight: "Bolder", wrap: true, text: "🔴 \(.name)" }, { type: "TextBlock", spacing: "None", isSubtle: true, size: "Small", wrap: true, - text: "\(.conclusion) · [Run-Log öffnen](\(.url))" } + text: "[Run-Log öffnen](\(.url))" } ] } ] ) From 0d36b2d0484c687fcc3d911e28169c5b0420ae72 Mon Sep 17 00:00:00 2001 From: ShodiBoy1 Date: Tue, 30 Jun 2026 09:17:43 +0200 Subject: [PATCH 4/6] CI status watch --- .github/workflows/ci-status-watch.yml | 41 ++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci-status-watch.yml b/.github/workflows/ci-status-watch.yml index 09c870f19..a8d61fb2a 100644 --- a/.github/workflows/ci-status-watch.yml +++ b/.github/workflows/ci-status-watch.yml @@ -1,9 +1,9 @@ name: CI status watch -# Posts a Teams message ONLY when a workflow on the default branch turns red -# (green -> red). For each workflow it compares the two latest runs with gh; a change -# is reported only if the newest run finished within the look-back window, so a workflow -# that stays red is not repeated every day. +# Posts a Teams message ONLY when one of the README badge workflows on the default +# branch turns red (green -> red). For each it compares the two latest runs with gh; a +# change is reported only if the newest run finished within the look-back window, so a +# workflow that stays red is not repeated every day. # Requires the repository secret TEAMS_WEBHOOK_URL. on: @@ -22,34 +22,37 @@ jobs: WEBHOOK: ${{ secrets.TEAMS_WEBHOOK_URL }} REPO: ${{ github.repository }} BRANCH: ${{ github.event.repository.default_branch }} - SELF: .github/workflows/ci-status-watch.yml steps: - - name: Detect newly red workflows and notify Teams + - name: Detect newly red badge workflows and notify Teams run: | set -euo pipefail + # The workflows backing the README badges. Add a line here if a badge is added. + WORKFLOWS=" + .github/workflows/build.yml + .github/workflows/update-urls.yml + .github/workflows/nightly-build.yml + .github/workflows/integration-tests.yml + " + # Monday catches up the weekend (Fri-Sun); other weekdays look back 24h. if [ "$(date -u +%u)" -eq 1 ]; then SINCE=$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ) else SINCE=$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ) fi - echo "Checking $REPO for workflows that turned red since $SINCE" + echo "Checking badge workflows for red since $SINCE" is_red() { [ "$1" = "failure" ] || [ "$1" = "timed_out" ] || [ "$1" = "startup_failure" ]; } - # For each active workflow, compare its two latest runs and report it only if it - # just turned red and that run finished inside the window (so a lasting red is - # reported just once, not every day). - RED=$(gh api "repos/$REPO/actions/workflows" \ - --jq '.workflows[] | select(.state=="active") | [.name, .path] | @tsv' \ - | while IFS=$'\t' read -r NAME WF_PATH; do - [ "$WF_PATH" = "$SELF" ] && continue + # Compare each workflow's two latest runs; report it only if it just turned red + # and that run finished inside the window (so a lasting red is reported once). + RED=$(for WF_PATH in $WORKFLOWS; do ROW=$(gh run list --repo "$REPO" --workflow "$WF_PATH" --branch "$BRANCH" \ - --status completed --limit 2 --json conclusion,url,updatedAt \ - --jq '[(.[0].conclusion // ""), (.[0].updatedAt // ""), (.[0].url // ""), (.[1].conclusion // "")] | @tsv' || true) + --status completed --limit 2 --json conclusion,url,updatedAt,workflowName \ + --jq '[(.[0].conclusion // ""), (.[0].updatedAt // ""), (.[0].url // ""), (.[1].conclusion // ""), (.[0].workflowName // "")] | @tsv' || true) [ -z "$ROW" ] && continue - IFS=$'\t' read -r CUR CURTIME URL PREV <<< "$ROW" + IFS=$'\t' read -r CUR CURTIME URL PREV NAME <<< "$ROW" [ -z "$CUR" ] && continue [[ "$CURTIME" > "$SINCE" ]] || continue if is_red "$CUR" && { [ "$PREV" = "success" ] || [ -z "$PREV" ]; }; then @@ -59,10 +62,10 @@ jobs: COUNT=$(echo "$RED" | jq 'length') if [ "$COUNT" -eq 0 ]; then - echo "No workflow turned red. Skipping Teams notification." + echo "No badge workflow turned red. Skipping Teams notification." exit 0 fi - echo "$COUNT workflow(s) turned red." + echo "$COUNT badge workflow(s) turned red." if [ -z "${WEBHOOK:-}" ]; then echo "No Teams webhook set, skipping notification."; exit 0 From a613459af9271978967dde069992927cb0ecab00 Mon Sep 17 00:00:00 2001 From: ShodiBoy1 Date: Wed, 1 Jul 2026 23:03:22 +0200 Subject: [PATCH 5/6] filter observer by daily team list instead of quthor association --- .github/workflows/issue-pr-observer.yml | 51 +++++++++++++++++-------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/.github/workflows/issue-pr-observer.yml b/.github/workflows/issue-pr-observer.yml index f132fc6fa..70304ff1b 100644 --- a/.github/workflows/issue-pr-observer.yml +++ b/.github/workflows/issue-pr-observer.yml @@ -2,7 +2,7 @@ name: External Issue, PR & Discussion Observer # Posts a weekday-morning digest of new EXTERNAL issues, PRs and discussions (last 24h) # to a Teams channel so incoming activity from outside the team is noticed quickly and -# doesn't slip. "External" = author association is not MEMBER, OWNER or COLLABORATOR. +# "External" = author is not in the DAILY_TEAM list below. # Issues/PRs come from the REST search API, discussions from GraphQL (no REST equivalent). # Each source falls back to empty on error, so one failing API still posts the rest. # Requires the repository secret TEAMS_WEBHOOK_URL (a Teams "Workflows" incoming webhook). @@ -27,7 +27,29 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} WEBHOOK: ${{ secrets.TEAMS_WEBHOOK_URL }} REPO: ${{ github.repository }} + # Daily-team logins to exclude (Project #5 "daily" tab); one per line, case-insensitive. + DAILY_TEAM: | + AdemZarrouki + Ali-Shariati-Najafabadi + areinicke + Caylipp + hohwille + JoelAdbu + KarimALotfy + laert-ll + laim2003 + marceltchanga9 + maybeec + oanding-blrng + Paras14 + quando632 + shodiBoy1 + tineff96 + vivu001 run: | + # so a failing gh api trips the || fallback instead of being hidden by the trailing jq + set -o pipefail + # Monday catches up the weekend (Fri–Sun); other weekdays look back 24h. if [ "$(date -u +%u)" -eq 1 ]; then SINCE=$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ) @@ -36,14 +58,16 @@ jobs: fi echo "Scanning $REPO for items created since $SINCE" - # Issues + PRs via REST search; keep only external authors. On error fall back to - # an empty list so a discussions failure (below) can still be reported on its own. + # team logins as a lowercase JSON array + TEAM=$(printf '%s\n' "$DAILY_TEAM" | grep -vE '^[[:space:]]*$' \ + | tr -d ' \t\r' | tr '[:upper:]' '[:lower:]' | jq -R . | jq -s .) + echo "Excluding $(echo "$TEAM" | jq 'length') daily-team member(s)." + + # Issues + PRs via REST search; drop team authors, keep the rest. On error use []. ISSUE_ITEMS=$(gh api -X GET search/issues \ - -f q="repo:$REPO created:>=$SINCE" \ - --jq '[.items[] - | select(.author_association != "MEMBER" - and .author_association != "OWNER" - and .author_association != "COLLABORATOR") + -f q="repo:$REPO created:>=$SINCE" -f per_page=100 --jq '.items' \ + | jq --argjson team "$TEAM" '[.[] + | select((.user.login | ascii_downcase) as $u | $team | index($u) | not) | { kind: (if .pull_request then "Pull Request" else "Issue" end), icon: (if .pull_request then "🔵" else "🟢" end), @@ -54,8 +78,7 @@ jobs: }]') \ || { echo "WARN: issue/PR fetch failed, continuing without them."; ISSUE_ITEMS='[]'; } - # Discussions have no REST search, so use GraphQL: take the newest, then filter by - # the same time window and external-author rule. 50 is plenty for a daily window. + # No REST search for discussions; take newest 50 via GraphQL, filter by window + team. OWNER=${REPO%/*} NAME=${REPO#*/} DISC_ITEMS=$(gh api graphql \ @@ -64,16 +87,14 @@ jobs: query($owner:String!, $name:String!) { repository(owner:$owner, name:$name) { discussions(first:50, orderBy:{field:CREATED_AT, direction:DESC}) { - nodes { number title url createdAt authorAssociation author { login } } + nodes { number title url createdAt author { login } } } } }' \ --jq '.data.repository.discussions.nodes' \ - | jq --arg since "$SINCE" '[ .[] + | jq --arg since "$SINCE" --argjson team "$TEAM" '[ .[] | select(.createdAt >= $since) - | select(.authorAssociation != "MEMBER" - and .authorAssociation != "OWNER" - and .authorAssociation != "COLLABORATOR") + | select(((.author.login // "") | ascii_downcase) as $u | $team | index($u) | not) | { kind: "Discussion", icon: "💬", From 163bc41236b8b3a4dc29b8a3b7be9a07c5b79219 Mon Sep 17 00:00:00 2001 From: T9 <103292348+shodiBoy1@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:05:39 +0200 Subject: [PATCH 6/6] Update issue-pr-observer.yml --- .github/workflows/issue-pr-observer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-pr-observer.yml b/.github/workflows/issue-pr-observer.yml index 70304ff1b..c85d6687a 100644 --- a/.github/workflows/issue-pr-observer.yml +++ b/.github/workflows/issue-pr-observer.yml @@ -27,7 +27,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} WEBHOOK: ${{ secrets.TEAMS_WEBHOOK_URL }} REPO: ${{ github.repository }} - # Daily-team logins to exclude (Project #5 "daily" tab); one per line, case-insensitive. + # Daily-team logins to exclude; one per line, case-insensitive. DAILY_TEAM: | AdemZarrouki Ali-Shariati-Najafabadi