Slack API로 Github PR 리뷰어 넛지

누군가가 검토하기를 기다리는 Pull Request가 있지만 며칠이 지나고 아무도 그것을 보지 않는 순간을 알고 있으므로 공개 채널에서 또는 개인적으로 검토자에게 수동으로 요청해야 합니다. 이것을 어떻게 자동화할 수 있습니까?

이전에 Bitbucket을 사용하고 있었고Bitbucket Cloud Slack의 리뷰어를 넛지(nudge)하는 멋진 기능이 있으며 리뷰어에게 Pull Request를 살펴보라고 알리는 비공개 메시지를 보냅니다.

지금 저는 Github를 Github for Slack과 함께 사용하고 있으며 하루 중 특정 시간에 리뷰어에게 PR에 대해 상기시키는 기능이 있습니다. 하지만 넛지 리뷰어 기능을 찾지 못했습니다. Slack 앱 스토어에서 검색했지만 여전히 성공하지 못했습니다.

공개적으로 사용할 수 없는 내 프로젝트와 팀을 위해 MVP를 하기로 결정했지만 프로젝트/팀/회사에 대해 동일한 기능을 만들 수 있습니다.

슬랙 앱



Slack 작업 공간에 메시지를 게시하려면 Slack App .
  • 새 앱 만들기 버튼 클릭
  • 처음부터 선택
  • 앱 이름을 <Company name> PR nudge (또는 다른 적절한 이름)
  • 로 설정
  • 회사 작업 공간을 선택하십시오
  • 앱 만들기 누르기

  • 다음으로 Slack 채널용 웹훅을 생성해야 합니다.
  • 기능 및 기능 추가 섹션에서 수신 웹훅을 선택하십시오.
  • 작업 공간에 새 웹훅 추가를 클릭하고 Github for Slack의 모든 메시지가 가는 채널을 선택하십시오(일반적으로 이 채널의 이름은 #dev).

  • 이제 채널에 메시지를 게시할 웹훅이 있습니다. 다음으로 어딘가에서 이 웹훅을 호출해야 합니다.

    사용자 스크립트



    Github PR 페이지에 검토자 Nudge 버튼을 추가하는 사용자 스크립트를 작성해 보겠습니다.

    사용자 스크립트에 대해 모르는 경우 내 설명 비디오를 확인할 수 있습니다: You Don't Know *.user.js .

    먼저 Userscript 관리자 확장이 필요합니다. 설치해 보겠습니다. Tampermonkey .
    설치 후 Tampermonkey를 열고 새 스크립트를 만듭니다.

    사용자 스크립트 헤더



    우리는 다음과 같이 시작합니다.

    // ==UserScript==
    // @name         <Your Company> PR nudge
    // @namespace    https://github.com/<Your Company>
    // @version      1.0.0
    // @description  Nudge PR reviewers
    // @author       You
    // @match        https://github.com/<Your Company>/*/pull/*
    // @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
    // @grant        GM_xmlhttpRequest
    // @connect      slack.com
    // ==/UserScript==
    (function () {
      "use strict";
      // Your code here...
    })();
    


    다음이 필요합니다.
  • @match https://github.com/<Your Company>/*/pull/* 다음과 같은 PR 페이지로 이동할 때만 스크립트를 실행합니다.https://github.com/Org/Repo/pull/4605
  • @grant GM_xmlhttpRequest Slack 웹훅에 대한 요청 수행
  • @connect slack.com Slack에 대한 요청 허용

  • 리뷰어 넛지 버튼



    이제 Nudge reviewers 버튼을 만들어 리뷰어 섹션에 추가해 보겠습니다.

    (function () {
      "use strict";
      const PR_NUDGE_WEBHOOK =
        "https://hooks.slack.com/services/********/***********/************************";
      const nudgeButton = document.createElement("button");
      nudgeButton.innerText = "Nudge reviewers";
      nudgeButton.className =
        "btn btn-sm js-details-target d-inline-block float-left float-none m-0 mr-md-0 js-title-edit-button";
      const reviewersFormElement = document.getElementById(
        "reviewers-select-menu"
      ).parentElement;
    })();
    




    검토가 필요한 사람



    이 함수는 이 PR을 검토해야 하는 Github 닉네임이 있는 목록을 반환합니다.

    function getAllAwaiting() {
      const allReviewers = Array.from(reviewersFormElement.querySelectorAll("p"));
      const awaiting = allReviewers.filter((reviewerEl) =>
        reviewerEl
          .querySelector("span[aria-label]")
          .getAttribute("aria-label")
          .startsWith("Awaiting")
      );
      const awaitingNicknames = awaiting.map(
        (reviewerEl) => reviewerEl.querySelectorAll("a")[1].innerText
      );
      return awaitingNicknames;
    }
    


    Slack 사용자와 Github 사용자 매핑



    여기 이 스크립트의 안타까운 부분이 있습니다. 왜냐하면 우리는 Github을 Slack과 매핑하여 Slack을 넛지할 대상을 알아야 하기 때문입니다.
    이 시점에서 이것은 수동 프로세스이며 이것이 이 솔루션의 유일한 단점입니다.

    (function () {
      "use strict";
      const GithubSlackMap = {
        strdr4605: "U**********",
        ...
      };
      const PR_NUDGE_WEBHOOK =
      ...
    


    Slack에서 회원 ID를 찾으려면 Slack 사용자 프로필로 이동하여 자세히를 클릭하고 회원 ID 복사를 클릭합니다.
    팀에 30명 이상의 엔지니어가 있는 경우 시간이 걸릴 수 있습니다. 저에게는 11명의 엔지니어였습니다.

    If you have an idea how to automate this, let me know.

    onNudgeBtnClick

    Now let's gather all the data and sent a request to Slack webhook.
    Slack has a specific format style for messages, so to tag people we need slackMention function.



    function slackMention(githugNickname) {
      return GithubSlackMap[githugNickname]
        ? `<@${GithubSlackMap[githugNickname]}>`
        : githugNickname;
    }
    function onNudgeBtnClick(e) {
      e.preventDefault();
      const prLink = window.location.href;
      const prTitle = document.title.split(" by ")[0] || prLink;
      const awaitingReviewers = getAllAwaiting();
      if (!awaitingReviewers.length) {
        return;
      }
      const dataObj = {
        text: `Hey ${awaitingReviewers.map(
          slackMention
        )},\nI really need your review on <${prLink}|${prTitle}>. 🥺😇🙏😊`,
      };
      GM_xmlhttpRequest({
        fetch: true,
        method: "POST",
        url: PR_NUDGE_WEBHOOK,
        data: JSON.stringify(dataObj),
        responseType: "json",
        nocache: true,
        onload: (response) => console.log({ response }),
        onerror: (err) => console.error({ err }),
      });
    }
    


    페이지에 버튼 추가




    nudgeButton.addEventListener("click", onNudgeBtnClick);
    setTimeout(() => {
      reviewersFormElement.appendChild(nudgeButton);
    }, 1000);
    


    최종 결과




    // ==UserScript==
    // @name         <Your Company> PR nudge
    // @namespace    https://github.com/<Your Company>
    // @version      1.0.0
    // @description  Nudge PR reviewers
    // @author       You
    // @match        https://github.com/<Your Company>/*/pull/*
    // @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
    // @grant        GM_xmlhttpRequest
    // @connect      slack.com
    // ==/UserScript==
    (function () {
      "use strict";
      const GithubSlackMap = {
        strdr4605: "U**********",
        ...
      };
      const PR_NUDGE_WEBHOOK =
        "https://hooks.slack.com/services/********/***********/************************";
      const nudgeButton = document.createElement("button");
      nudgeButton.innerText = "Nudge reviewers";
      nudgeButton.className =
        "btn btn-sm js-details-target d-inline-block float-left float-none m-0 mr-md-0 js-title-edit-button";
      const reviewersFormElement = document.getElementById(
        "reviewers-select-menu"
      ).parentElement;
      function getAllAwaiting() {
        const allReviewers = Array.from(reviewersFormElement.querySelectorAll("p"));
        const awaiting = allReviewers.filter((reviewerEl) =>
          reviewerEl
            .querySelector("span[aria-label]")
            .getAttribute("aria-label")
            .startsWith("Awaiting")
        );
        const awaitingNicknames = awaiting.map(
          (reviewerEl) => reviewerEl.querySelectorAll("a")[1].innerText
        );
        return awaitingNicknames;
      }
      function slackMention(githugNickname) {
        return GithubSlackMap[githugNickname]
          ? `<@${GithubSlackMap[githugNickname]}>`
          : githugNickname;
      }
      function onNudgeBtnClick(e) {
        e.preventDefault();
        const prLink = window.location.href;
        const prTitle = document.title.split(" by ")[0] || prLink;
        const awaitingReviewers = getAllAwaiting();
        if (!awaitingReviewers.length) {
          return;
        }
        const dataObj = {
          text: `Hey ${awaitingReviewers.map(
            slackMention
          )},\nI really need your review on <${prLink}|${prTitle}>. 🥺😇🙏😊`,
        };
        GM_xmlhttpRequest({
          fetch: true,
          method: "POST",
          url: PR_NUDGE_WEBHOOK,
          data: JSON.stringify(dataObj),
          responseType: "json",
          nocache: true,
          onload: (response) => console.log({ response }),
          onerror: (err) => console.error({ err }),
        });
      }
      nudgeButton.addEventListener("click", onNudgeBtnClick);
      setTimeout(() => {
        reviewersFormElement.appendChild(nudgeButton);
      }, 1000);
    })();
    


    이제 검토자 넛지(Nudge reviewers)를 클릭하면 팀원에게 알림이 전송되고 변경 사항을 더 빠르게 검토할 것입니다.

    마지막 단계



    Don't expose webhook URL to the public!



    Github 조직의 비공개 저장소( company-userscripts )에 스크립트를 추가합니다.
    이름을 PR-nudge.user.js .
    팀원들에게 Tampermonkey를 설치하고 보내라고 말합니다.
    https://github.com/Org/org-userscripts/blob/master/PR-nudge.user.js?raw=1
    컴퓨터에 스크립트를 추가합니다.

    좋은 웹페이지 즐겨찾기