diff --git a/dev-support/Jenkinsfile_GitHub b/dev-support/Jenkinsfile_GitHub index 0813168e7f64..c456f2d6d628 100644 --- a/dev-support/Jenkinsfile_GitHub +++ b/dev-support/Jenkinsfile_GitHub @@ -92,6 +92,7 @@ pipeline { DOCKERFILE = "${WORKDIR}/${DOCKERFILE_REL}" YETUS_DRIVER = "${WORKDIR}/${YETUS_DRIVER_REL}" ASF_NIGHTLIES_GENERAL_CHECK_BASE="${ASF_NIGHTLIES_BASE}/${WORKDIR_REL}/${PATCH_REL}" + HIDE_OLD_COMMENTS = "${SOURCEDIR}/dev-support/gh_hide_old_comments.sh" } when { // this will return true if the pipeline is building a change request, such as a GitHub pull request. @@ -174,6 +175,19 @@ pipeline { reportFiles: 'report.html', reportName: 'PR General Check Report' ] + withCredentials([ + usernamePassword( + credentialsId: 'apache-hbase-at-github.zerozr99.workers.dev', + passwordVariable: 'GITHUB_TOKEN', + usernameVariable: 'GITHUB_USER' + ) + ]) { + script { + sh label: 'Hide Stale PR Comments', script: '''#!/bin/bash -e + "${HIDE_OLD_COMMENTS}" "${CHANGE_ID}" || : + ''' + } + } } // Jenkins pipeline jobs fill slaves on PRs without this :( cleanup() { diff --git a/dev-support/gh_hide_old_comments.sh b/dev-support/gh_hide_old_comments.sh new file mode 100755 index 000000000000..61217cfa241b --- /dev/null +++ b/dev-support/gh_hide_old_comments.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -eEuo pipefail +trap 'exit 1' ERR + +declare DEBUG +if [ "${DEBUG}" = 'true' ] ; then + set -x +fi + +declare GITHUB_TOKEN +declare -a GITHUB_AUTH +GITHUB_AUTH=('--header' "Authorization: token ${GITHUB_TOKEN}") +declare GITHUB_API_URL="https://api.github.com" + +# the user_id of the buildbot that leaves the comments we want to hide. +declare BUILD_BOT_USER_ID="${BUILD_BOT_USER_ID:-49303554}" +declare REPO="${REPO:-apache/hbase}" +declare JOB_NAME="${JOB_NAME:-HBase-PreCommit-GitHub-PR}" +JOB_NAME="$(echo "${JOB_NAME}" | cut -d/ -f1)" +declare CURL="${CURL:-curl}" + +function fetch_comments { + local pr="$1" + local comments_file + local -a curl_args + curl_args=( + --fail + "${GITHUB_AUTH[@]}" + --header 'Accept: application/vnd.github+json' + --header 'X-GitHub-Api-Version: 2022-11-28' + --request GET + --url "${GITHUB_API_URL}/repos/${REPO}/issues/${pr}/comments?per_page=500" + ) + if [ "${DEBUG}" = true ] ; then + curl_args+=(--verbose) + else + curl_args+=(--silent) + fi + + comments_file="$(mktemp "comments_${pr}" 2>/dev/null || mktemp -t "comments_${pr}.XXXXXXXXXX")" || \ + { >&2 echo 'cannot create temp file'; exit 1 ;} + "${CURL}" "${curl_args[@]}" > "${comments_file}" + if [ "${DEBUG}" = 'true' ] ; then + >&2 cat "${comments_file}" + fi + echo "${comments_file}" +} + +function hide_old_comment { + local pr="$1" + local comment_id="$2" + local -a curl_args + local query + read -r -d '' query << EOF || : +mutation { + minimizeComment(input: { subjectId: \"$comment_id\", classifier: OUTDATED }) { + minimizedComment { + isMinimized, + minimizedReason + } + } +} +EOF + # remove newlines and whitespace rather than escaping them + query="${query//[$'\r\n\t ']/}" + + curl_args=( + --fail + "${GITHUB_AUTH[@]}" + --header 'Accept: application/vnd.github+json' + --header 'Content-Type: application/json' + --header 'X-GitHub-Api-Version: 2022-11-28' + --request POST + --url "${GITHUB_API_URL}/graphql" + ) + if [ "${DEBUG}" = true ] ; then + curl_args+=(--verbose) + else + curl_args+=(--silent) + fi + + "${CURL}" "${curl_args[@]}" --data "{ \"query\": \"${query}\" }" +} + +function identify_most_recent_build_number { + local pr="$1" + local comments_file="$2" + local jq_filter + read -r -d '' jq_filter << EOF || : +.[] \ +| select(.user.id == ${BUILD_BOT_USER_ID}) \ +| .body \ +| capture("${JOB_NAME}/job/PR-${pr}/(?[0-9]+)/") \ +| .buildnum +EOF + + jq -r "${jq_filter}" "${comments_file}" \ + | sort -nu \ + | tail -n1 +} + +function identify_old_comment_ids { + local pr="$1" + local comments_file="$2" + local most_recent_build_number="$3" + local jq_filter + read -r -d '' jq_filter << EOF || : +.[] \ +| select(.user.id == ${BUILD_BOT_USER_ID}) \ +| { node_id, buildnum: (.body | capture("${JOB_NAME}/job/PR-${pr}/(?[0-9]+)/") | .buildnum | tonumber) } \ +| select(.buildnum < (${most_recent_build_number} | tonumber)) \ +| .node_id +EOF + + jq -r "${jq_filter}" "${comments_file}" +} + +function main { + local pr="$1" + local comments_file + local most_recent_build_number + + comments_file="$(fetch_comments "$pr")" + most_recent_build_number="$(identify_most_recent_build_number "${pr}" "${comments_file}")" + for comment_id in $(identify_old_comment_ids "${pr}" "${comments_file}" "${most_recent_build_number}") ; do + hide_old_comment "${pr}" "${comment_id}" + sleep 1 + done +} + +main "$@"