haikuwebkit/Tools/WebKitBot/src/WKR.mjs

106 lines
3.7 KiB
JavaScript
Raw Permalink Normal View History

Add Slack-aware WebKitBot implementation https://bugs.webkit.org/show_bug.cgi?id=211707 Reviewed by Devin Rousso and Brian Burg. This patch adds Slack-aware WebKitBot implementation which supports "ping", "revert", and the other commands. I decided to implement this feature in chat-bot because of the following reasons. 1. It is the previous way we are familiar with. 2. Creating a revert-patch sometimes takes long time because it involves working-copy clean-up, creating a revert, generating a patch... So asynchronous request-response is better for developers who do not want to watch whether the patch is created until it is done. Chat-bot can tell us when it is ready. 3. Requesting a revert-patch in public channel can easily tell the other developers that reverting is going on now. We reuse python's `webkit-patch create-revert` command as it was in the old webkitbot. We enhance WKR bot to support webkitbot feature. Once webkitbot app is installed into a channel, it monitors messages in the channel and works when the mention to this bot's user happens. We use Real Time Message Slack API instead of Event API, because Event API requires public facing HTTPS server which accepts incoming webhook. Real Time Message Slack API just requires a server app which connects to Slack via WebSocket, so it is easier to deploy for now. The implementation of webkitbot is client-server model inside one process. There is one AsyncTaskQueue, and there is one async main loop which takes the task from the queue and replies when it is done. Every time we noticed the request, this request queues the task to this task queue. This design ensures that only one task is using the working-copy WebKit repository at a time, this is required to make a revert-patch without conflict. * WKR/ReadMe.md: Removed. * WKR/WKR.mjs: Removed. * WKR/package-lock.json: Removed. * WKR/package.json: Removed. * WebKitBot/.eslintrc: Added. * WebKitBot/.gitignore: Renamed from Tools/WKR/.gitignore. * WebKitBot/ReadMe.md: Added. * WebKitBot/data/.gitignore: Renamed from Tools/WKR/data/.gitignore. * WebKitBot/package-lock.json: Added. * WebKitBot/package.json: Added. * WebKitBot/src/AsyncTaskQueue.mjs: Added. * WebKitBot/src/Commit.mjs: Added. * WebKitBot/src/Contributors.mjs: Added. * WebKitBot/src/Utility.mjs: Added. * WebKitBot/src/WKR.mjs: Added. * WebKitBot/src/WebKitBot.mjs: Added. * WebKitBot/src/index.mjs: Added. * WebKitBot/tests/Commit.test.mjs: Added. * WebKitBot/tests/WebKitBot.test.mjs: Added. * WebKitBot/tests/resources/.gitattributes: Added. * WebKitBot/tests/resources/HaveRadarAndBugzilla.json: Added. * WebKitBot/tests/resources/HavingBugzilla.json: Added. * WebKitBot/tests/resources/NoRadarAndBugzilla.json: Added. Canonical link: https://commits.webkit.org/226978@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@264210 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2020-07-10 07:02:30 +00:00
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import RSS from "rss-parser";
import storage from "node-persist";
import axios from "axios";
import Commit from "./Commit.mjs";
import Contributors from "./Contributors.mjs";
import {dataLogLn} from "./Utility.mjs";
// Change to true when debugging to avoid posting notifications to Slack.
const DRY = false;
const commitEndpointURL = "https://git.webkit.org/?p=WebKit-https.git;a=atom";
const defaultInterval = 60 * 1000;
async function sleep(milliseconds)
{
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
export default class WKR {
constructor(webClient, auth, revision)
{
this._web = webClient;
this._auth = auth;
this._revision = revision;
}
async postToSlack(commit)
{
let data = {
text: commit.message(),
};
dataLogLn(data);
if (!DRY)
await axios.post(process.env.slackURL, JSON.stringify(data));
await sleep(500);
}
async action()
{
dataLogLn(`${Date.now()}: poll data`);
let contributors = await Contributors.create();
let parser = new RSS;
let response = await parser.parseURL(commitEndpointURL);
let commits = response.items.map((feedItem) => new Commit(feedItem, contributors));
commits.sort((a, b) => a.revision - b.revision);
if (this._revision) {
commits = commits.filter((commit) => commit.revision > this._revision);
for (let commit of commits)
await this.postToSlack(commit);
}
let latestCommit = commits[commits.length - 1];
if (latestCommit) {
this._revision = latestCommit.revision;
await storage.setItem("revision", this._revision);
}
}
static async create(webClient, auth)
{
let revision = await storage.getItem("revision");
dataLogLn(`Previous Revision: ${revision}`);
dataLogLn(`Endpoint: ${process.env.slackURL}`);
return new WKR(webClient, auth, revision);
}
static async main(webClient, auth)
{
let bot = await WKR.create(webClient, auth);
while (true) {
let start = Date.now();
try {
await bot.action();
} catch (error) {
console.error(error);
}
await sleep(Math.max(defaultInterval - (Date.now() - start), 0));
}
}
}