Skip to content
Snippets Groups Projects
Unverified Commit 01840901 authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Backport reminder bot (#43637)

* WIP backport reminder bot

* polish and automate backport reminder

* do better english
parent 4b66bdba
Branches
Tags
No related merge requests found
name: Release Status Check
name: Release and Backport Status Check
on:
workflow_dispatch:
......@@ -38,3 +38,31 @@ jobs:
repo: context.repo.repo,
channelName: process.env.SLACK_RELEASE_CHANNEL,
});
open-backport-reminder:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: release
- name: Prepare build scripts
run: cd ${{ github.workspace }}/release && yarn --frozen-lockfile && yarn build
- name: Check release status and post to Slack
uses: actions/github-script@v7
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
script: | # js
const { checkOpenBackports } = require('${{ github.workspace }}/release/dist/index.cjs');
if (!process.env.SLACK_BOT_TOKEN) {
throw new Error('SLACK_BOT_TOKEN is required');
}
const ossReleased = await checkOpenBackports({
github,
owner: context.repo.owner,
repo: context.repo.repo,
channelName: 'engineering-ci',
});
{
"overrides": [
{
"files": ["*.ts", "*.js"],
"rules": {
"no-console": "off"
}
}
]
}
import "dotenv/config";
import type { Octokit } from "@octokit/rest";
import dayjs from 'dayjs';
import { sendBackportReminder } from "./slack";
const RECENT_BACKPORT_THRESHOLD_HOURS = 8;
/** check open backports and send slack reminders about stale ones */
export const checkOpenBackports = async ({ github, owner, repo, channelName }: {
github: Octokit,
owner: string,
repo: string,
channelName: string,
}) => {
const { data: openBackports } = await github.issues.listForRepo({
owner,
repo,
labels: "was-backported",
state: "open",
});
console.log(`Found ${openBackports.length} open backports`);
const staleBackports = openBackports
.filter(issue => dayjs().diff(dayjs(issue.created_at), 'hours') > RECENT_BACKPORT_THRESHOLD_HOURS);
if (!staleBackports.length) {
console.log("No recent backports to remind about");
return;
}
console.log(`Reminding about ${staleBackports.length} stale backports`);
sendBackportReminder({
channelName,
backports: staleBackports,
});
}
export * from "./backports";
export * from "./github";
export * from "./release-notes";
export * from "./release-status";
......
import { getMilestoneIssues, isLatestRelease } from "./github";
import type { ReleaseProps, Issue } from "./types";
import {
isEnterpriseVersion,
isRCVersion,
isValidVersionString,
} from "./version-helpers";
import type { ReleaseProps, Issue } from "./types";
const releaseTemplate = `**Upgrading**
......@@ -38,8 +38,12 @@ SHA-256 checksum for the {{version}} JAR:
`;
const isBugIssue = (issue: Issue) =>
issue.labels.some(tag => tag.name === "Type:Bug");
const isBugIssue = (issue: Issue) => {
if (typeof issue.labels === 'string') {
return issue.labels.includes("Type:Bug");
}
return issue.labels.some(tag => tag.name === "Type:Bug");
}
const formatIssue = (issue: Issue) => `- ${issue.title} (#${issue.number})`;
......
import { WebClient } from '@slack/web-api';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
import _githubSlackMap from "../github-slack-map.json";
const githubSlackMap: Record<string, string> = _githubSlackMap;
......@@ -11,7 +14,7 @@ import { getGenericVersion } from "./version-helpers";
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
const SLACK_CHANNEL_NAME = process.env.SLACK_RELEASE_CHANNEL ?? "bot-testing";
export function mentionUserByGithubLogin(githubLogin: string | null) {
export function mentionUserByGithubLogin(githubLogin?: string | null) {
if (githubLogin && githubLogin in githubSlackMap) {
return `<@${githubSlackMap[githubLogin]}>`;
}
......@@ -27,6 +30,62 @@ export function getChannelTopic(channelName: string) {
});
}
function formatBackportItem(issue: Omit<Issue, 'labels'>,) {
const age = dayjs(issue.created_at).fromNow();
return `${mentionUserByGithubLogin(issue.assignee?.login)} - ${slackLink(issue.title, issue.html_url)} - ${age}`;
}
export async function sendBackportReminder({
channelName, backports,
}: {
channelName: string,
backports: Omit<Issue, 'labels'>[],
}) {
const text = backports
.reverse()
.map(formatBackportItem).join("\n");
const blocks = [
{
"type": "header",
"text": {
"type": "plain_text",
"text": `:shame-conga: ${backports.length} Open Backports :shame-conga:`,
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `_${
slackLink('See all open backports','https://github.com/metabase/metabase/pulls?q=is%3Aopen+is%3Apr+label%3Awas-backported')} | ${
slackLink('Should I backport this?', 'https://www.notion.so/metabase/Metabase-Branching-Strategy-6eb577d5f61142aa960a626d6bbdfeb3?pvs=4#89f80d6f17714a0198aeb66c0efd1b71')}_`,
}
},
];
const attachments = [
{
"color": "#F9841A",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": text,
}
}],
},
];
return slack.chat.postMessage({
channel: channelName,
blocks,
attachments,
text: `${backports.length} open backports`,
});
}
export async function sendPreReleaseStatus({
channelName, version, date, openIssues, closedIssueCount, milestoneId
}: {
......
......@@ -39,6 +39,7 @@ export type Issue = {
number: number;
title: string;
html_url: string;
labels: { name: string }[];
assignee: { login: string };
labels: string | { name?: string }[];
assignee: null | { login: string };
created_at: string;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment