Skip to content

Commit

Permalink
Merge pull request #77 from zeebe-io/referred-issues
Browse files Browse the repository at this point in the history
Referred issues placeholder
  • Loading branch information
korthout authored Jun 27, 2021
2 parents 2b99472 + e812bda commit 2052b75
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ The following placeholders are available and are replaced with:

Placeholder | Replaced with
------------|------------
`issue_refs` | GitHub issue references to all issues mentioned in the original pull request description seperated by a space, e.g. `#123 #456 zeebe-io/backport-action#789`
`pull_number` | The number of the original pull request that is backported, e.g. `123`
`target_branch`| The branchname to which the pull request is backported, e.g. `release-0.23`

Expand Down
57 changes: 54 additions & 3 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions src/backport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dedent from "dedent";
import { CreatePullRequestResponse, RequestReviewersResponse } from "./github";
import { GithubApi } from "./github";
import * as exec from "./exec";
import * as utils from "./utils";

type PRContent = {
title: string;
Expand Down Expand Up @@ -136,7 +137,8 @@ export class Backport {
const { title, body } = this.composePRContent(
target,
mainpr.title,
pull_number
pull_number,
mainpr.body
);
const new_pr_response = await this.github.createPR({
owner,
Expand Down Expand Up @@ -209,12 +211,15 @@ export class Backport {
private composePRContent(
target: string,
issue_title: string,
issue_number: number
issue_number: number,
original_body: string
): PRContent {
const title = `[Backport ${target}] ${issue_title}`;
const issues = utils.getMentionedIssueRefs(original_body);
const body = this.config.pull.description
.replace("${pull_number}", issue_number.toString())
.replace("${target_branch}", target);
.replace("${target_branch}", target)
.replace("${issue_refs}", issues.join(" "));
return { title, body };
}

Expand Down
1 change: 1 addition & 0 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class Github implements GithubApi {
export type PullRequest = {
number: number;
title: string;
body: string;
head: {
sha: string;
};
Expand Down
152 changes: 152 additions & 0 deletions src/test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import dedent from "dedent";
import { getMentionedIssueRefs } from "../utils";

describe("get mentioned issues", () => {
describe("returns an empty list", () => {
it("for an empty text", () => {
expect(getMentionedIssueRefs("")).toHaveLength(0);
});

it("for a text without mentioned issues", () => {
expect(getMentionedIssueRefs(text({}))).toHaveLength(0);
});

it("for a text with an issue reference as part of a word", () => {
expect(getMentionedIssueRefs(text({ part: "#123" }))).toHaveLength(0);
});

it("for a text with an external issue reference as part of a word", () => {
expect(
getMentionedIssueRefs(text({ part: "zeebe-io/zeebe#123" }))
).toHaveLength(0);
});

it("for a text with an issue url as part of a word", () => {
expect(
getMentionedIssueRefs(
text({ part: "github.com/zeebe-io/backport-action/issues/123" })
)
).toHaveLength(0);
});
});

describe("returns a single reference", () => {
it("for a text with an issue reference at the start", () => {
expect(getMentionedIssueRefs(text({ start: "#123" }))).toEqual(["#123"]);
});
it("for a text with an issue reference in the middle", () => {
expect(getMentionedIssueRefs(text({ middle: "#123" }))).toEqual(["#123"]);
});
it("for a text with an issue reference at the end", () => {
expect(getMentionedIssueRefs(text({ end: "#123" }))).toEqual(["#123"]);
});

it("for a text with an external issue reference at the start", () => {
expect(
getMentionedIssueRefs(text({ start: "zeebe-io/zeebe#123" }))
).toEqual(["zeebe-io/zeebe#123"]);
});
it("for a text with an external issue reference in the middle", () => {
expect(
getMentionedIssueRefs(text({ middle: "zeebe-io/zeebe#123" }))
).toEqual(["zeebe-io/zeebe#123"]);
});
it("for a text with an external issue reference at the end", () => {
expect(
getMentionedIssueRefs(text({ end: "zeebe-io/zeebe#123" }))
).toEqual(["zeebe-io/zeebe#123"]);
});

it("for a text with an issue url at the start", () => {
expect(
getMentionedIssueRefs(
text({ start: "github.com/zeebe-io/backport-action/issues/123/" })
)
).toEqual(["zeebe-io/backport-action#123"]);
});
it("for a text with an issue url in the middle", () => {
expect(
getMentionedIssueRefs(
text({ middle: "github.com/zeebe-io/backport-action/issues/123" })
)
).toEqual(["zeebe-io/backport-action#123"]);
});
it("for a text with an issue url at the end", () => {
expect(
getMentionedIssueRefs(
text({ end: "github.com/zeebe-io/backport-action/issues/123" })
)
).toEqual(["zeebe-io/backport-action#123"]);
});
});

describe("returns all references", () => {
it("for a text with an issue reference at the start, middle and end", () => {
expect(
getMentionedIssueRefs(
text({ start: "#123", middle: "#234", end: "#345" })
)
).toEqual(["#123", "#234", "#345"]);
});
it("for a text with an external issue reference at the start, middle and end", () => {
expect(
getMentionedIssueRefs(
text({
start: "zeebe-io/zeebe#123",
middle: "zeebe-io/zeebe#234",
end: "zeebe-io/zeebe#345",
})
)
).toEqual([
"zeebe-io/zeebe#123",
"zeebe-io/zeebe#234",
"zeebe-io/zeebe#345",
]);
});
it("for a text with an issue url at the start, middle and end", () => {
const base = "github.com/zeebe-io/backport-action/issues/";
expect(
getMentionedIssueRefs(
text({ start: `${base}123`, middle: `${base}234`, end: `${base}345` })
)
).toEqual([
"zeebe-io/backport-action#123",
"zeebe-io/backport-action#234",
"zeebe-io/backport-action#345",
]);
});
it("for a text with an external issue url at the start, middle and end", () => {
const base = "github.com/zeebe-io/zeebe/issues/";
expect(
getMentionedIssueRefs(
text({ start: `${base}123`, middle: `${base}234`, end: `${base}345` })
)
).toEqual([
"zeebe-io/zeebe#123",
"zeebe-io/zeebe#234",
"zeebe-io/zeebe#345",
]);
});
});

// todo deal with urls to unrelated repos
});

function text({
start = "",
middle = "",
end = "",
part = "",
}: {
start?: string;
middle?: string;
end?: string;
part?: string;
}) {
return dedent`${start ?? ""} foo bar
bar bar ${middle ?? ""} bar
foo/${part ?? ""} foo${part ?? ""}foo ${part ?? ""}foo
foo bar bar foo ${end ?? ""}`;
}
43 changes: 43 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const patterns = {
// matches urls to github issues at start, middle, end of line as individual word
// may be lead and trailed by whitespace which should be trimmed
// captures the `org`, `repo` and `number` of the issue
// https://regex101.com/r/XKRl8q/5
url: {
global:
/(?:^| )(?:(?:https:\/\/)?(?:www\.)?github\.com\/(?<org>[^ \/\n]+)\/(?<repo>[^ \/\n]+)\/issues\/(?<number>[0-9]+)(?:\/)?)(?: |$)/gm,
first:
/(?:^| )(?:(?:https:\/\/)?(?:www\.)?github\.com\/(?<org>[^ \/\n]+)\/(?<repo>[^ \/\n]+)\/issues\/(?<number>[0-9]+)(?:\/)?)(?: |$)/m,
},

// matches `#123` at start, middle, end of line as individual word
// may be lead and trailed by whitespace which should be trimmed
// captures `number` of the issue (and optionally the `org` and `repo`)
// https://regex101.com/r/2gAB8O/2
ref: /(?:^| )((?<org>[^\n #\/]+)\/(?<repo>[^\n #\/]+))?#(?<number>[0-9]+)(?: |$)/gm,
};

/**
* @param body Text in which to search for mentioned issues
* @returns All found mentioned issues as GitHub issue references
*/
export function getMentionedIssueRefs(body: string): string[] {
const issueUrls =
body.match(patterns.url.global)?.map((url) => toRef(url)) ?? [];
const issueRefs = body.match(patterns.ref) ?? [];
return issueUrls.concat(issueRefs).map((ref) => ref.trim());
}

const toRef = (url: string) => {
// matchAll is not yet available to directly access the captured groups of all matches
// so this maps the urls to GitHub refs by matching again without the global flag
const result = patterns.url.first.exec(url);
if (!result) {
console.error(
`Expected to transform url (${url}) to GitHub reference, but it did not match pattern'`
);
return "";
}
const [, org, repo, number] = result;
return `${org}/${repo}#${number}`;
};

0 comments on commit 2052b75

Please sign in to comment.