Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions app/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import { ReopenCommand } from "./commands/reopen-command.js";
import { LabelCommand } from "./commands/label-command.js";
import { RemoveLabelCommand } from "./commands/remove-label-command.js";
import { ReviewerCommand } from "./commands/reviewer-command.js";
import { HelpCommand } from "./commands/help-command.js";

export const ALL_COMMANDS = [
TransferCommand,
CloseCommand,
ReopenCommand,
LabelCommand,
RemoveLabelCommand,
ReviewerCommand,
HelpCommand,
];

export function getCommands(id, payload) {
const commands = [
new TransferCommand(id, payload),
new CloseCommand(id, payload),
new ReopenCommand(id, payload),
new LabelCommand(id, payload),
new RemoveLabelCommand(id, payload),
new ReviewerCommand(id, payload),
];
const commands = ALL_COMMANDS.map((Cls) => new Cls(id, payload));
return commands.filter((command) => command.matches());
}
10 changes: 10 additions & 0 deletions app/commands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,14 @@ describe("commands", () => {

expect(commands).toHaveLength(2);
});

test("getCommands matches /help command", () => {
const commands = getCommands("any", {
comment: {
body: "/help",
},
});

expect(commands).toHaveLength(1);
});
});
10 changes: 10 additions & 0 deletions app/commands/close-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ import {
const classLogger = getLogger("commands/close-command");

export class CloseCommand extends Command {
static configKey = "close";

constructor(id, payload) {
super(id, payload);
}

get usage() {
return "/close [not-planned]";
}

get description() {
return "Close this issue";
}

matches() {
return closeMatcher(extractBody(this.payload));
}
Expand Down
8 changes: 8 additions & 0 deletions app/commands/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ export class Command {
this.payload = payload;
}

get usage() {
throw new Error("usage must be implemented");
}

get description() {
throw new Error("description must be implemented");
}

matches() {
throw new Error("matches() must be implemented");
}
Expand Down
68 changes: 68 additions & 0 deletions app/commands/help-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { helpMatcher } from "../matchers.js";
import { reportError } from "../github.js";
import { Command } from "./command.js";
import { getLogger } from "../logger.js";
import {
extractBody,
extractHtmlUrl,
extractLabelableId,
} from "../comment-extractor.js";
import { ALL_COMMANDS } from "../commands.js";

const classLogger = getLogger("commands/help-command");

function buildHelpText(config) {
const rows = ALL_COMMANDS.filter(
(Cls) => !Cls.configKey || config.commands[Cls.configKey]?.enabled,
)
.map(
(Cls) => `| \`${Cls.prototype.usage}\` | ${Cls.prototype.description} |`,
)
.join("\n");

return `### Available Commands\n\n| Command | Description |\n| ------- | ----------- |\n${rows}`;
}

export class HelpCommand extends Command {
get usage() {
return "/help";
}

get description() {
return "Show available commands";
}

matches() {
return helpMatcher(extractBody(this.payload));
}

enabled(config) {
this._config = config;
return {
enabled: true,
};
}

async run(authToken) {
const logger = classLogger.child({
user: this.payload.sender.login,
id: this.id,
});

logger.info(`Showing help for ${extractHtmlUrl(this.payload)}`);
try {
await reportError(
authToken,
extractLabelableId(this.payload),
buildHelpText(this._config),
);
} catch (error) {
logger.error(
`Failed to post help comment ${
error.errors ? JSON.stringify(error.errors) : ""
}`,
error,
);
}
}
}
10 changes: 10 additions & 0 deletions app/commands/label-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@ import {
const classLogger = getLogger("commands/label-command");

export class LabelCommand extends Command {
static configKey = "label";

constructor(id, payload) {
super(id, payload);
}

get usage() {
return "/label <label1,label2>";
}

get description() {
return "Add labels to this issue or pull request";
}

matches() {
return labelMatcher(extractBody(this.payload));
}
Expand Down
10 changes: 10 additions & 0 deletions app/commands/remove-label-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ import {
const classLogger = getLogger("commands/remove-label-command");

export class RemoveLabelCommand extends Command {
static configKey = "removeLabel";

constructor(id, payload) {
super(id, payload);
}

get usage() {
return "/remove-label <label1,label2>";
}

get description() {
return "Remove labels from this issue or pull request";
}

matches() {
return removeLabelMatcher(extractBody(this.payload));
}
Expand Down
10 changes: 10 additions & 0 deletions app/commands/reopen-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ import {
const classLogger = getLogger("commands/reopen-command");

export class ReopenCommand extends Command {
static configKey = "reopen";

constructor(id, payload) {
super(id, payload);
}

get usage() {
return "/reopen";
}

get description() {
return "Reopen this issue";
}

matches() {
return reopenMatcher(extractBody(this.payload));
}
Expand Down
10 changes: 10 additions & 0 deletions app/commands/reviewer-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ import {
const classLogger = getLogger("commands/reviewer-command");

export class ReviewerCommand extends Command {
static configKey = "reviewer";

constructor(id, payload) {
super(id, payload);
}

get usage() {
return "/reviewer <reviewer1,reviewer2>";
}

get description() {
return "Request reviewers for this pull request";
}

matches() {
return reviewerMatcher(extractBody(this.payload));
}
Expand Down
10 changes: 10 additions & 0 deletions app/commands/transfer-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ import {
const classLogger = getLogger("commands/transfer-command");

export class TransferCommand extends Command {
static configKey = "transfer";

constructor(id, payload) {
super(id, payload);
}

get usage() {
return "/transfer <repo-name>";
}

get description() {
return "Transfer this issue to another repository";
}

matches() {
return transferMatcher(extractBody(this.payload));
}
Expand Down
4 changes: 4 additions & 0 deletions app/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ export function removeLabelMatcher(text) {
export function reviewerMatcher(text) {
return text.match(/(?:^| | \r\n|\n)\/reviewers? ([ /@A-Za-z\d-,]+)/);
}

export function helpMatcher(text) {
return text.match(/(?:^| | \r\n|\n)\/help/);
}
24 changes: 24 additions & 0 deletions app/matchers.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
closeMatcher,
helpMatcher,
labelMatcher,
removeLabelMatcher,
reopenMatcher,
Expand Down Expand Up @@ -240,4 +241,27 @@ describe("matchers", () => {
expect(result).toBeFalsy();
});
});

describe("help", () => {
test("matches /help", () => {
const result = helpMatcher("/help");

expect(result).toBeTruthy();
});
test("does not match input without /help", () => {
const result = helpMatcher("help");

expect(result).toBeFalsy();
});
test("does not match in the middle of a string", () => {
const result = helpMatcher("something/help");

expect(result).toBeFalsy();
});
test("matches /help at the start of a new line", () => {
const result = helpMatcher("some text\n/help");

expect(result).toBeTruthy();
});
});
});
Loading