From 5bd4576c9b30801eb8c351707d95e45588b773cf Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 21 Oct 2026 15:58:40 +0300 Subject: [PATCH 1/4] =?UTF-8?q?fix(ci):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=80=D0=B5=D0=BB=D0=B8=D0=B7=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B2=D0=B5=D1=82=D0=BA=D0=B8=20=D0=B2=20=D1=83?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_notifications.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_notifications.yml b/.github/workflows/pr_notifications.yml index aded99d7..1d759e36 100644 --- a/.github/workflows/pr_notifications.yml +++ b/.github/workflows/pr_notifications.yml @@ -2,7 +2,7 @@ name: PR Notifications on: pull_request_target: - branches: [main] + branches: [main, 'release/**'] types: [opened, synchronize, edited, reopened, ready_for_review] permissions: From 5585e68168ac9f1616c6c3e42156cb22f811be2c Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 21 Oct 2026 16:05:59 +0300 Subject: [PATCH 2/4] =?UTF-8?q?fix(ci):=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B2=D1=8C=D1=8E=D0=B5=D1=80=D0=B0=20=D0=B8=20=D1=83=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_notifications.yml | 147 ++++++++++++------------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/.github/workflows/pr_notifications.yml b/.github/workflows/pr_notifications.yml index 1d759e36..3a567b24 100644 --- a/.github/workflows/pr_notifications.yml +++ b/.github/workflows/pr_notifications.yml @@ -16,100 +16,91 @@ concurrency: jobs: notify: runs-on: ubuntu-latest - if: github.event.pull_request.draft != true env: - MM_USERS_JSON: "${{ secrets.MM_USERS }}" - PR_AUTHOR: "${{ github.event.pull_request.user.login }}" - GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + MM_USERS_JSON: ${{ secrets.MM_USERS }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} steps: - - name: Timeout + - name: Wait for automatic reviewer assignment + if: github.event.pull_request.draft != true run: sleep 1m - - name: Get Available Reviewers - run: | - AVAILABLE_REVIEWERS_ENTRIES=$( - echo "$MM_USERS_JSON" | - jq -c --arg exclude "$PR_AUTHOR" 'to_entries | map(select(.key != $exclude))' - ) - echo "::add-mask::$AVAILABLE_REVIEWERS_ENTRIES" - echo "AVAILABLE_REVIEWERS_ENTRIES=$AVAILABLE_REVIEWERS_ENTRIES" >> $GITHUB_ENV - - name: Check Requested Reviewers - run: | - REQUESTED_REVIEWERS_LOGIN_LIST=$(gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json reviewRequests --jq '.reviewRequests | map(select(.login)) | map(.login)') - SELECTED_REVIEWER_ENTRY=$( - echo "$AVAILABLE_REVIEWERS_ENTRIES" | - jq -c --argjson requested "$REQUESTED_REVIEWERS_LOGIN_LIST" 'first(.[] | select(.key as $gh_login | $requested | index($gh_login)))' - ) - echo "::add-mask::$SELECTED_REVIEWER_ENTRY" - echo "SELECTED_REVIEWER_ENTRY=$SELECTED_REVIEWER_ENTRY" >> $GITHUB_ENV - - name: Check Last Review + + - name: Load Pull Request + id: pr + shell: bash run: | - if [ -z "$SELECTED_REVIEWER_ENTRY" ]; then - LAST_REVIEW=$(gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json reviews --jq '.reviews | last') - LAST_REVIEWER_LOGIN=$(echo "$LAST_REVIEW" | jq -r '.author.login') - SELECTED_REVIEWER_ENTRY=$( - echo $AVAILABLE_REVIEWERS_ENTRIES | - jq -c --arg lastReviewerLogin "$LAST_REVIEWER_LOGIN" 'map(select(.key == $lastReviewerLogin)) | first' - ) - echo "::add-mask::$SELECTED_REVIEWER_ENTRY" - echo "LAST_REVIEW=$LAST_REVIEW" >> $GITHUB_ENV + gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \ + --json number,url,author,headRefName,state,isDraft,reviewRequests,reviews \ + > "$RUNNER_TEMP/review-pr.json" - if [[ "$SELECTED_REVIEWER_ENTRY" != "null" ]]; then - echo "SELECTED_REVIEWER_ENTRY=$SELECTED_REVIEWER_ENTRY" >> $GITHUB_ENV - else - echo "Last review check failed" - fi + if ! jq -e '.state == "OPEN" and (.isDraft | not)' "$RUNNER_TEMP/review-pr.json" > /dev/null; then + echo "::notice::Уведомления доступны только для открытых PR, готовых к ревью." + echo "ready=false" >> "$GITHUB_OUTPUT" else - echo "The reviewer was selected on the previous steps. Skip checking last review" + echo "ready=true" >> "$GITHUB_OUTPUT" fi - - name: Assign Reviewer + - name: Select Reviewer + id: reviewer + if: steps.pr.outputs.ready == 'true' + shell: bash run: | - if [ -z "$SELECTED_REVIEWER_ENTRY" ]; then - AVAILABLE_REVIEWERS_LENGTH=$(echo "$AVAILABLE_REVIEWERS_ENTRIES" | jq 'length') - SELECTED_REVIEWER_INDEX=$((RANDOM % AVAILABLE_REVIEWERS_LENGTH)) - SELECTED_REVIEWER_ENTRY=$(echo "$AVAILABLE_REVIEWERS_ENTRIES" | jq -c --argjson index "$SELECTED_REVIEWER_INDEX" '.[$index]') + PR_JSON="$RUNNER_TEMP/review-pr.json" + PR_AUTHOR=$(jq -r '.author.login' "$PR_JSON") + AVAILABLE_REVIEWERS=$(jq -c --arg author "$PR_AUTHOR" \ + 'to_entries | map(select(.key != $author))' <<< "$MM_USERS_JSON") + REQUESTED_REVIEWERS=$(jq -c '[.reviewRequests[] | .login // empty]' "$PR_JSON") + LAST_REVIEWER=$(jq -r '.reviews | last | .author.login // ""' "$PR_JSON") + + SELECTED_REVIEWER=$(jq -c --argjson requested "$REQUESTED_REVIEWERS" \ + 'map(select(.key as $login | $requested | index($login))) | first' <<< "$AVAILABLE_REVIEWERS") + HAS_REQUESTED_REVIEWER=false + if [[ "$SELECTED_REVIEWER" != "null" ]]; then + HAS_REQUESTED_REVIEWER=true else - echo "The reviewer was selected on the previous steps. Skip assigning a reviewer" + SELECTED_REVIEWER=$(jq -c --arg login "$LAST_REVIEWER" \ + 'map(select(.key == $login)) | first' <<< "$AVAILABLE_REVIEWERS") fi - REVIEWER_GH=$(echo "$SELECTED_REVIEWER_ENTRY" | jq -r '.key') - REVIEWER_MM=$(echo "$SELECTED_REVIEWER_ENTRY" | jq -r '.value') - - gh pr edit ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --add-reviewer "$REVIEWER_GH" + if [[ "$SELECTED_REVIEWER" == "null" ]]; then + REVIEWER_COUNT=$(jq 'length' <<< "$AVAILABLE_REVIEWERS") + if [[ "$REVIEWER_COUNT" -eq 0 ]]; then + echo "::error::В MM_USERS нет доступного ревьюера, кроме автора PR." + exit 1 + fi + SELECTED_REVIEWER=$(jq -c --argjson index "$((RANDOM % REVIEWER_COUNT))" \ + '.[$index]' <<< "$AVAILABLE_REVIEWERS") + fi + echo "::add-mask::$SELECTED_REVIEWER" + REVIEWER_GH=$(jq -r '.key' <<< "$SELECTED_REVIEWER") echo "::add-mask::$REVIEWER_GH" - echo "REVIEWER_GH=$REVIEWER_GH" >> $GITHUB_ENV - - echo "::add-mask::$REVIEWER_MM" - echo "REVIEWER_MM=$REVIEWER_MM" >> $GITHUB_ENV - - name: Check Notification Need - run: | - PR_STATE=$(echo "$LAST_REVIEW" | jq -r '.state') - REVIEWER_LOGIN=$(echo "$LAST_REVIEW" | jq -r '.author.login') + echo "$SELECTED_REVIEWER" > "$RUNNER_TEMP/review-recipient.json" - if [[ "$PR_STATE" == "APPROVED" ]] && [[ "$REVIEWER_LOGIN" == "$REVIEWER_GH" ]]; then - NEED_NOTIFY=0 - else - NEED_NOTIFY=1 + if [[ "$HAS_REQUESTED_REVIEWER" == "false" ]] && \ + jq -e --arg login "$REVIEWER_GH" \ + '.reviews | last | .state == "APPROVED" and .author.login == $login' "$PR_JSON" > /dev/null; then + echo "::notice::Ревьюер уже одобрил PR." + echo "notify=false" >> "$GITHUB_OUTPUT" + exit 0 fi - echo "NEED_NOTIFY=$NEED_NOTIFY" >> $GITHUB_ENV - - name: "Notify Reviewer" - run: | - if [ "$NEED_NOTIFY" -ne 0 ]; then - BRANCH_URL="${{ github.event.pull_request.head.repo.html_url }}/tree/${{ github.event.pull_request.head.ref }}" - - MESSAGE="MR [!${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }})" - MESSAGE+=" от [${{ github.event.pull_request.user.login }} ${USER_NAME}]" - MESSAGE+="(${{ github.event.pull_request.user.html_url }})" - MESSAGE+="(ветка: [${{ github.event.pull_request.head.ref }}](${BRANCH_URL})) готов на ревью.\n" - MESSAGE+="Выбран ревьюер: @${REVIEWER_MM}.\n" - MESSAGE+="Ссылка на MR: ${{ github.event.pull_request.html_url }}" + gh pr edit "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --add-reviewer "$REVIEWER_GH" + echo "notify=true" >> "$GITHUB_OUTPUT" - curl -X POST \ + - name: Notify Reviewer + if: steps.reviewer.outputs.notify == 'true' + env: + REVIEW_CHAT_WEBHOOK: ${{ secrets.REVIEW_CHAT_WEBHOOK }} + shell: bash + run: | + REVIEWER_MM=$(jq -r '.value' "$RUNNER_TEMP/review-recipient.json") + echo "::add-mask::$REVIEWER_MM" + jq --arg reviewer "$REVIEWER_MM" --arg server "$GITHUB_SERVER_URL" \ + '{text: ("PR [#\(.number)](\(.url)) от [\(.author.login)](\($server)/\(.author.login)) " + + "(ветка: \(.headRefName)) готов к ревью.\n" + + "Ревьюер: @\($reviewer).\nСсылка на PR: \(.url)")}' \ + "$RUNNER_TEMP/review-pr.json" | \ + curl --fail-with-body --silent --show-error \ -H "Content-Type: application/json" \ - -d "{\"text\": \"$MESSAGE\"}" \ - "${{ secrets.REVIEW_CHAT_WEBHOOK }}" - else - echo "Notification is not needed. Skip notification step" - fi + --data-binary @- "$REVIEW_CHAT_WEBHOOK" From 51f53b9b1f63db9271d6c1c02db246197010c4d6 Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 21 Oct 2026 16:06:33 +0300 Subject: [PATCH 3/4] =?UTF-8?q?feat(ci):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=80=D1=83=D1=87=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=B8=20=D1=83?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_notifications.yml | 74 +++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr_notifications.yml b/.github/workflows/pr_notifications.yml index 3a567b24..3651169a 100644 --- a/.github/workflows/pr_notifications.yml +++ b/.github/workflows/pr_notifications.yml @@ -4,31 +4,83 @@ on: pull_request_target: branches: [main, 'release/**'] types: [opened, synchronize, edited, reopened, ready_for_review] + issue_comment: + types: [created] + workflow_dispatch: + inputs: + pr_number: + description: 'Номер PR' + required: true + type: string + mode: + description: 'Режим запуска' + required: true + default: full + type: choice + options: + - full + - notify-only permissions: contents: read pull-requests: write -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - jobs: + authorize-comment: + if: >- + github.event_name == 'issue_comment' && github.event.issue.pull_request && + (github.event.comment.body == '/review' || github.event.comment.body == + '/notify') + runs-on: ubuntu-latest + steps: + - name: Check Comment Author Permission + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMENT_AUTHOR: ${{ github.event.comment.user.login }} + shell: bash + run: | + PERMISSION=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$COMMENT_AUTHOR/permission" --jq '.permission') + if [[ "$PERMISSION" != "write" && "$PERMISSION" != "admin" ]]; then + echo "::error::Команды ревью доступны пользователям с правом записи в репозиторий." + exit 1 + fi + notify: + needs: authorize-comment + if: >- + !cancelled() && (github.event_name != 'issue_comment' || + needs.authorize-comment.result == 'success') runs-on: ubuntu-latest + concurrency: + group: + ${{ github.workflow }}-${{ inputs.pr_number || + github.event.pull_request.number || github.event.issue.number }} + cancel-in-progress: true env: MM_USERS_JSON: ${{ secrets.MM_USERS }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} + PR_NUMBER: + ${{ inputs.pr_number || github.event.pull_request.number || + github.event.issue.number }} + MODE: + ${{ inputs.mode || (github.event_name == 'issue_comment' && + github.event.comment.body == '/notify' && 'notify-only') || 'full' }} steps: - name: Wait for automatic reviewer assignment - if: github.event.pull_request.draft != true + if: + github.event_name == 'pull_request_target' && + github.event.pull_request.draft != true run: sleep 1m - name: Load Pull Request id: pr shell: bash run: | + if [[ ! "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then + echo "::error::Укажите положительный номер PR." + exit 1 + fi + gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \ --json number,url,author,headRefName,state,isDraft,reviewRequests,reviews \ > "$RUNNER_TEMP/review-pr.json" @@ -63,6 +115,10 @@ jobs: fi if [[ "$SELECTED_REVIEWER" == "null" ]]; then + if [[ "$MODE" == "notify-only" ]]; then + echo "::error::Нет текущего или последнего ревьюера из MM_USERS. Запустите режим full." + exit 1 + fi REVIEWER_COUNT=$(jq 'length' <<< "$AVAILABLE_REVIEWERS") if [[ "$REVIEWER_COUNT" -eq 0 ]]; then echo "::error::В MM_USERS нет доступного ревьюера, кроме автора PR." @@ -77,7 +133,7 @@ jobs: echo "::add-mask::$REVIEWER_GH" echo "$SELECTED_REVIEWER" > "$RUNNER_TEMP/review-recipient.json" - if [[ "$HAS_REQUESTED_REVIEWER" == "false" ]] && \ + if [[ "$GITHUB_EVENT_NAME" == "pull_request_target" && "$HAS_REQUESTED_REVIEWER" == "false" ]] && \ jq -e --arg login "$REVIEWER_GH" \ '.reviews | last | .state == "APPROVED" and .author.login == $login' "$PR_JSON" > /dev/null; then echo "::notice::Ревьюер уже одобрил PR." @@ -85,7 +141,9 @@ jobs: exit 0 fi - gh pr edit "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --add-reviewer "$REVIEWER_GH" + if [[ "$MODE" == "full" ]]; then + gh pr edit "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --add-reviewer "$REVIEWER_GH" + fi echo "notify=true" >> "$GITHUB_OUTPUT" - name: Notify Reviewer From e5859c799d666c8b527b769b3dccb076ed69e27f Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 21 Oct 2026 16:24:03 +0300 Subject: [PATCH 4/4] =?UTF-8?q?fix(ci):=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D1=8B=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_notifications.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_notifications.yml b/.github/workflows/pr_notifications.yml index 3651169a..07700f58 100644 --- a/.github/workflows/pr_notifications.yml +++ b/.github/workflows/pr_notifications.yml @@ -3,7 +3,7 @@ name: PR Notifications on: pull_request_target: branches: [main, 'release/**'] - types: [opened, synchronize, edited, reopened, ready_for_review] + types: [opened, reopened, ready_for_review] issue_comment: types: [created] workflow_dispatch: