Skip to content
Merged
1 change: 1 addition & 0 deletions .github/label-codeowners.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"data-classification": "@netwrix/dataclassification-docs",
"directory-manager": "@netwrix/directorymanager-docs",
"endpoint-policy-manager": "@netwrix/endpointpolicymanager-docs",
"policypak": "@netwrix/endpointpolicymanager-docs",
"endpoint-protector": "@netwrix/endpointprotector-docs",
"identity-manager": "@netwrix/identitymanager-docs",
"identity-recovery": "@netwrix/recoveryforactivedirectory-docs",
Expand Down
29 changes: 29 additions & 0 deletions .github/team-slack-map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"_comment": "Maps each CODEOWNERS/label-codeowners team to the Slack member IDs to @-mention in #docs-gh. Find a member ID via their Slack profile -> \"...\" menu -> Copy member ID. Empty arrays post without an @-mention until filled in.",
"@netwrix/1secure-docs": [],
"@netwrix/accessanalyzer-docs": [],
"@netwrix/accessinformationcenter-docs": [],
"@netwrix/activitymonitor-docs": [],
"@netwrix/auditor-docs": [],
"@netwrix/changetracker-docs": [],
"@netwrix/dataclassification-docs": [],
"@netwrix/directorymanager-docs": [],
"@netwrix/endpointpolicymanager-docs": [],
"@netwrix/endpointprotector-docs": [],
"@netwrix/identitymanager-docs": [],
"@netwrix/passwordpolicyenforcer-docs": [],
"@netwrix/passwordreset-docs": [],
"@netwrix/passwordsecure-docs": [],
"@netwrix/pingcastle-docs": [],
"@netwrix/platgovnetsuite-docs": [],
"@netwrix/platgovnetsuiteflashlight-docs": [],
"@netwrix/platgovsalesforce-docs": [],
"@netwrix/platgovsalesforceflashlight-docs": [],
"@netwrix/privilegesecure-docs": [],
"@netwrix/privilegesecurediscovery-docs": [],
"@netwrix/recoveryforactivedirectory-docs": [],
"@netwrix/threatmanager-docs": [],
"@netwrix/threatprevention-docs": [],
"@netwrix/kb-docs": [],
"@netwrix/training-docs": []
}
97 changes: 97 additions & 0 deletions .github/workflows/slack-notify-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Slack Notify Issue

# Posts a Slack message to #docs-gh whenever an Issue is created or commented on,
# @-mentioning the relevant CODEOWNERS team members.

on:
issues:
types: [opened]
issue_comment:
types: [created]

concurrency:
group: slack-notify-issue-${{ github.event.issue.number }}
cancel-in-progress: false

permissions:
contents: read
issues: read

jobs:
notify:
# Bot-authored comments (Vale autofix, Claude reviewers, etc.) are
# excluded to avoid Slack spam from automated commentary.
if: ${{ !github.event.issue.pull_request && (github.event_name != 'issue_comment' || github.event.comment.user.type != 'Bot') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
.github
scripts
sparse-checkout-cone-mode: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'

- name: Determine codeowner teams
id: codeowners
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
# Fetch current labels from the issue. Labels may not be applied yet
# (auto-labeling runs asynchronously), so an empty result here just
# means no team mention — it does not skip the notification itself.
LABELS=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json labels --jq '.labels[].name')

# Skip for KB Operations tracking issues (any kb/ label)
if echo "$LABELS" | grep -q "^kb/"; then
echo "Issue has kb/ label — skipping Slack notification"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi

# Skip for Admin Support PR review tracking issues
if echo "$ISSUE_TITLE" | grep -q "^Admin: PR review"; then
echo "Admin Support tracking issue — skipping Slack notification"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi

# Read the mapping file
MAPPING=$(cat .github/label-codeowners.json)

# Collect matched teams (deduplicated), comma-joined
TEAMS=""
while IFS= read -r label; do
team=$(echo "$MAPPING" | jq -r --arg l "$label" '.[$l] // empty')
if [ -n "$team" ]; then
if ! echo "$TEAMS" | grep -qF "$team"; then
TEAMS="${TEAMS:+$TEAMS,}$team"
fi
fi
done <<< "$LABELS"

echo "teams=$TEAMS" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"

- name: Send Slack notification
if: steps.codeowners.outputs.skip != 'true'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
NOTIFY_TEAMS: ${{ steps.codeowners.outputs.teams }}
ISSUE_URL: ${{ github.event.issue.html_url }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
COMMENT_URL: ${{ github.event.comment.html_url }}
run: |
if [ "${{ github.event_name }}" = "issues" ]; then
MESSAGE="Issue created: $ISSUE_URL"
else
MESSAGE="Comment on Issue #$ISSUE_NUMBER: $COMMENT_URL"
fi

node scripts/notify-slack.mjs --message "$MESSAGE" --teams "$NOTIFY_TEAMS" || echo "Failed to send Slack notification"
106 changes: 106 additions & 0 deletions .github/workflows/slack-notify-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Slack notify PR activity

# Posts a Slack message to #docs-gh whenever a PR is created, has a review
# requested, or is commented on — @-mentioning the CODEOWNERS team(s) that
# own the changed files. Fork PRs also get a PR comment tagging codeowners,
# since they otherwise have no other reviewer visibility into who owns the
# changed area.

on:
# pull_request_target (not pull_request) so this job has repo secrets and
# a write-scoped token even for fork PRs. SAFETY: this workflow must never
# check out or execute the PR's head content — only read PR metadata via
# the GitHub API (gh pr view) and run code from the pinned base ref below.
# Adding a checkout of the fork's head ref here would reintroduce the
# exact secret-exfiltration risk pull_request_target is meant to avoid.
pull_request_target:
types: [opened, review_requested]
issue_comment:
types: [created]

permissions:
contents: read
pull-requests: write

concurrency:
group: slack-notify-pr-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: false

jobs:
notify:
# issue_comment fires for both issue comments and PR comments — only
# continue here when the comment is on a PR. Plain issue comments are
# handled by the separate issue-intake workflow. Bot-authored comments
# (Vale autofix, Claude reviewers, etc.) are excluded to avoid Slack
# spam from automated commentary.
if: (github.event_name == 'pull_request_target' || github.event.issue.pull_request) && (github.event_name != 'issue_comment' || github.event.comment.user.type != 'Bot')
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Always the base ref — see the SAFETY note above. Never point
# this at the PR head/merge ref.
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }}

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'

- name: Determine PR number
id: pr-info
run: |
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
else
echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT"
fi

- name: Resolve CODEOWNERS teams for changed files
id: resolve-teams
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
FILES=$(gh pr view ${{ steps.pr-info.outputs.number }} --repo ${{ github.repository }} --json files --jq '.files[].path')
mapfile -t FILE_ARRAY <<< "$FILES"
TEAMS=$(node scripts/resolve-codeowners-teams.mjs "${FILE_ARRAY[@]}")
echo "teams=$TEAMS" >> "$GITHUB_OUTPUT"

- name: Build message
id: build-message
run: |
# Build the notification text based on which event/action fired.
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.action }}" = "opened" ]; then
MESSAGE="PR created: ${{ github.event.pull_request.html_url }}"
elif [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.action }}" = "review_requested" ]; then
MESSAGE="Review requested on PR #${{ steps.pr-info.outputs.number }}: ${{ github.event.pull_request.html_url }}"
else
MESSAGE="Comment on PR #${{ steps.pr-info.outputs.number }}: ${{ github.event.comment.html_url }}"
fi
echo "message=$MESSAGE" >> "$GITHUB_OUTPUT"

- name: Comment on fork PRs tagging codeowners
# Fork contributors and reviewers may not be in the Slack channel,
# so give them a visible, on-PR codeowners tag too. Only needed
# once, on open — review_requested/comments already notify Slack.
if: >-
github.event_name == 'pull_request_target' &&
github.event.action == 'opened' &&
github.event.pull_request.head.repo.full_name != github.repository &&
steps.resolve-teams.outputs.teams != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NOTIFY_TEAMS: ${{ steps.resolve-teams.outputs.teams }}
run: |
MENTIONS=$(echo "$NOTIFY_TEAMS" | tr ',' ' ')
gh pr comment ${{ steps.pr-info.outputs.number }} --repo ${{ github.repository }} --body "Thanks for the contribution! Tagging codeowners for review: $MENTIONS"

- name: Send Slack notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
NOTIFY_MESSAGE: ${{ steps.build-message.outputs.message }}
NOTIFY_TEAMS: ${{ steps.resolve-teams.outputs.teams }}
run: |
node scripts/notify-slack.mjs --message "$NOTIFY_MESSAGE" --teams "$NOTIFY_TEAMS" || echo "Failed to send Slack notification"
24 changes: 23 additions & 1 deletion .github/workflows/sync-dev-to-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ env:
TARGET_BRANCH: main
# Teams webhook URL stored in GitHub Secrets for security
TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }}
# Slack webhook URL stored in GitHub Secrets for security
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

jobs:
sync-branches:
Expand All @@ -33,6 +35,11 @@ jobs:
fetch-depth: 0 # Full history for merge
token: ${{ secrets.SYNC_PAT }}

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'

- name: Configure git
run: |
git config user.name "github-actions[bot]"
Expand Down Expand Up @@ -62,7 +69,7 @@ jobs:
run: |
echo "⏳ Waiting for 'build-and-deploy.yml' workflow to complete on ${{ env.SOURCE_BRANCH }}..."

MAX_WAIT=1800 # 30 minutes max wait
MAX_WAIT=2700 # 45 minutes max wait
WAIT_INTERVAL=30 # Check every 30 seconds
ELAPSED=0

Expand Down Expand Up @@ -200,6 +207,11 @@ jobs:
}]
}' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification"

- name: Send success notification to Slack
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false'
run: |
node scripts/notify-slack.mjs --message "✅ dev→main sync succeeded: ${{ steps.merge-details.outputs.commit_count }} commits merged (latest ${{ steps.merge-details.outputs.commit_sha }}). ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" || echo "Failed to send Slack notification"

- name: Create GitHub issue for conflict
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
Expand Down Expand Up @@ -276,6 +288,11 @@ jobs:
}]
}' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification"

- name: Send conflict notification to Slack
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'true'
run: |
node scripts/notify-slack.mjs --message "⚠️ dev→main sync blocked by merge conflict: ${{ steps.check-new-commits.outputs.commits_ahead }} commits waiting. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" || echo "Failed to send Slack notification"

- name: Send skip notification to Teams
if: steps.check-new-commits.outputs.has_new_commits == 'false'
run: |
Expand All @@ -292,6 +309,11 @@ jobs:
}]
}' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification"

- name: Send failure notification to Slack
if: failure()
run: |
node scripts/notify-slack.mjs --message "❌ dev→main sync failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" || echo "Failed to send Slack notification"

- name: Workflow summary
if: always()
run: |
Expand Down
Loading
Loading