diff --git a/dist/index.js b/dist/index.js
index b27604a..bd93d46 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -173,11 +173,11 @@ class IssueContentParser {
.map(x => (0, utils_1.parseIssueUrl)(x))
.filter((x) => x !== null);
}
- extractIssueDependencies(issue) {
+ extractIssueDependencies(issue, repoRef) {
const contentLines = issue.body?.split("\n") ?? [];
return contentLines
.filter(x => this.isDependencyLine(x))
- .map(x => (0, utils_1.parseIssuesUrls)(x))
+ .map(x => (0, utils_1.parseIssuesUrls)(x, repoRef))
.flat()
.filter((x) => x !== null);
}
@@ -291,7 +291,7 @@ const run = async () => {
for (const issueRef of rootIssueTasklist) {
const issue = await githubApiClient.getIssue(issueRef);
const issueDetails = mermaid_node_1.MermaidNode.createFromGitHubIssue(issue);
- const issueDependencies = issueContentParser.extractIssueDependencies(issue);
+ const issueDependencies = issueContentParser.extractIssueDependencies(issue, issueRef);
graphBuilder.addIssue(issueRef, issueDetails);
issueDependencies.forEach(x => graphBuilder.addDependency(x, issueRef));
}
@@ -330,12 +330,13 @@ run();
/***/ }),
/***/ 235:
-/***/ ((__unused_webpack_module, exports) => {
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.MermaidNode = void 0;
+const utils_1 = __nccwpck_require__(918);
class MermaidNode {
constructor(nodeId, title, status, url) {
this.nodeId = nodeId;
@@ -343,22 +344,10 @@ class MermaidNode {
this.status = status;
this.url = url;
}
- getWrappedTitle() {
- const maxWidth = 40;
- const words = this.title.split(/\s+/);
- let result = words[0];
- let lastLength = result.length;
- for (let wordIndex = 1; wordIndex < words.length; wordIndex++) {
- if (lastLength + words[wordIndex].length >= maxWidth) {
- result += "\n";
- lastLength = 0;
- }
- else {
- result += " ";
- }
- result += words[wordIndex];
- lastLength += words[wordIndex].length;
- }
+ getFormattedTitle() {
+ let result = this.title;
+ result = result.replaceAll('"', "'");
+ result = (0, utils_1.wrapString)(result, 40);
return result;
}
static createFromGitHubIssue(issue) {
@@ -447,7 +436,7 @@ ${renderedGraphIssues}
`;
}
renderIssue(issue) {
- const title = issue.getWrappedTitle();
+ const title = issue.getFormattedTitle();
const linkedTitle = issue.url
? `${title}`
: title;
@@ -478,9 +467,10 @@ exports.MermaidRender = MermaidRender;
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.parseIssuesUrls = exports.parseIssueUrl = void 0;
+exports.wrapString = exports.parseIssuesUrls = exports.parseIssueNumber = exports.parseIssueUrl = void 0;
const issueUrlRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)$/i;
-const issueUrlsRegex = /github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/gi;
+const issueNumberRegex = /^#(\d+)$/;
+const issueUrlsRegex = /https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)|#\d+/gi;
const parseIssueUrl = (str) => {
const found = str.trim().match(issueUrlRegex);
if (!found) {
@@ -493,18 +483,47 @@ const parseIssueUrl = (str) => {
};
};
exports.parseIssueUrl = parseIssueUrl;
-const parseIssuesUrls = (str) => {
+const parseIssueNumber = (str, repoRef) => {
+ const found = str.trim().match(issueNumberRegex);
+ if (!found) {
+ return null;
+ }
+ return {
+ repoOwner: repoRef.repoOwner,
+ repoName: repoRef.repoName,
+ issueNumber: parseInt(found[1]),
+ };
+};
+exports.parseIssueNumber = parseIssueNumber;
+const parseIssuesUrls = (str, repoRef) => {
const result = [];
for (const match of str.matchAll(issueUrlsRegex)) {
- result.push({
- repoOwner: match[1],
- repoName: match[2],
- issueNumber: parseInt(match[3]),
- });
+ const parsedIssue = (0, exports.parseIssueUrl)(match[0]) || (0, exports.parseIssueNumber)(match[0], repoRef);
+ if (parsedIssue) {
+ result.push(parsedIssue);
+ }
}
return result;
};
exports.parseIssuesUrls = parseIssuesUrls;
+const wrapString = (str, maxWidth) => {
+ const words = str.split(/\s+/);
+ let result = words[0];
+ let lastLength = result.length;
+ for (let wordIndex = 1; wordIndex < words.length; wordIndex++) {
+ if (lastLength + words[wordIndex].length >= maxWidth) {
+ result += "\n";
+ lastLength = 0;
+ }
+ else {
+ result += " ";
+ }
+ result += words[wordIndex];
+ lastLength += words[wordIndex].length;
+ }
+ return result;
+};
+exports.wrapString = wrapString;
/***/ }),
diff --git a/src/issue-content-parser.ts b/src/issue-content-parser.ts
index 8b05ab7..88ba9cc 100644
--- a/src/issue-content-parser.ts
+++ b/src/issue-content-parser.ts
@@ -1,4 +1,4 @@
-import { GitHubIssue, GitHubIssueReference } from "./models";
+import { GitHubIssue, GitHubIssueReference, GitHubRepoReference } from "./models";
import { parseIssuesUrls, parseIssueUrl } from "./utils";
export class IssueContentParser {
@@ -12,12 +12,12 @@ export class IssueContentParser {
.filter((x): x is GitHubIssueReference => x !== null);
}
- public extractIssueDependencies(issue: GitHubIssue): GitHubIssueReference[] {
+ public extractIssueDependencies(issue: GitHubIssue, repoRef: GitHubRepoReference): GitHubIssueReference[] {
const contentLines = issue.body?.split("\n") ?? [];
return contentLines
.filter(x => this.isDependencyLine(x))
- .map(x => parseIssuesUrls(x))
+ .map(x => parseIssuesUrls(x, repoRef))
.flat()
.filter((x): x is GitHubIssueReference => x !== null);
}
diff --git a/src/main.ts b/src/main.ts
index b81185c..9a91d7c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -31,7 +31,7 @@ const run = async (): Promise => {
for (const issueRef of rootIssueTasklist) {
const issue = await githubApiClient.getIssue(issueRef);
const issueDetails = MermaidNode.createFromGitHubIssue(issue);
- const issueDependencies = issueContentParser.extractIssueDependencies(issue);
+ const issueDependencies = issueContentParser.extractIssueDependencies(issue, issueRef);
graphBuilder.addIssue(issueRef, issueDetails);
issueDependencies.forEach(x => graphBuilder.addDependency(x, issueRef));
}
diff --git a/src/mermaid-node.ts b/src/mermaid-node.ts
index 39fd3b0..0863e5e 100644
--- a/src/mermaid-node.ts
+++ b/src/mermaid-node.ts
@@ -1,4 +1,5 @@
import { GitHubIssue } from "./models";
+import { wrapString } from "./utils";
export type MermaidNodeStatus = "default" | "notstarted" | "started" | "completed";
@@ -10,23 +11,11 @@ export class MermaidNode {
public readonly url?: string
) {}
- public getWrappedTitle(): string {
- const maxWidth = 40;
- const words = this.title.split(/\s+/);
-
- let result = words[0];
- let lastLength = result.length;
- for (let wordIndex = 1; wordIndex < words.length; wordIndex++) {
- if (lastLength + words[wordIndex].length >= maxWidth) {
- result += "\n";
- lastLength = 0;
- } else {
- result += " ";
- }
-
- result += words[wordIndex];
- lastLength += words[wordIndex].length;
- }
+ public getFormattedTitle(): string {
+ let result = this.title;
+
+ result = result.replaceAll('"', "'");
+ result = wrapString(result, 40);
return result;
}
diff --git a/src/mermaid-render.ts b/src/mermaid-render.ts
index 0143099..2815c68 100644
--- a/src/mermaid-render.ts
+++ b/src/mermaid-render.ts
@@ -61,7 +61,7 @@ ${renderedGraphIssues}
}
private renderIssue(issue: MermaidNode): string {
- const title = issue.getWrappedTitle();
+ const title = issue.getFormattedTitle();
const linkedTitle = issue.url
? `${title}`
: title;
diff --git a/src/models.ts b/src/models.ts
index 755a567..83e3a2d 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -1,6 +1,9 @@
-export type GitHubIssueReference = {
+export type GitHubRepoReference = {
repoOwner: string;
repoName: string;
+};
+
+export type GitHubIssueReference = GitHubRepoReference & {
issueNumber: number;
};
diff --git a/src/utils.ts b/src/utils.ts
index b0f48d9..0c634a2 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,10 +1,11 @@
-import { GitHubIssueReference } from "./models";
+import { GitHubIssueReference, GitHubRepoReference } from "./models";
// Analogue of TypeScript "Partial" type but for null values
export type NullablePartial = { [P in keyof T]: T[P] | null | undefined };
const issueUrlRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)$/i;
-const issueUrlsRegex = /github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/gi;
+const issueNumberRegex = /^#(\d+)$/;
+const issueUrlsRegex = /https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)|#\d+/gi;
export const parseIssueUrl = (str: string): GitHubIssueReference | null => {
const found = str.trim().match(issueUrlRegex);
@@ -19,15 +20,47 @@ export const parseIssueUrl = (str: string): GitHubIssueReference | null => {
};
};
-export const parseIssuesUrls = (str: string): GitHubIssueReference[] => {
+export const parseIssueNumber = (str: string, repoRef: GitHubRepoReference): GitHubIssueReference | null => {
+ const found = str.trim().match(issueNumberRegex);
+ if (!found) {
+ return null;
+ }
+
+ return {
+ repoOwner: repoRef.repoOwner,
+ repoName: repoRef.repoName,
+ issueNumber: parseInt(found[1]),
+ };
+};
+
+export const parseIssuesUrls = (str: string, repoRef: GitHubRepoReference): GitHubIssueReference[] => {
const result: GitHubIssueReference[] = [];
for (const match of str.matchAll(issueUrlsRegex)) {
- result.push({
- repoOwner: match[1],
- repoName: match[2],
- issueNumber: parseInt(match[3]),
- });
+ const parsedIssue = parseIssueUrl(match[0]) || parseIssueNumber(match[0], repoRef);
+ if (parsedIssue) {
+ result.push(parsedIssue);
+ }
+ }
+
+ return result;
+};
+
+export const wrapString = (str: string, maxWidth: number): string => {
+ const words = str.split(/\s+/);
+
+ let result = words[0];
+ let lastLength = result.length;
+ for (let wordIndex = 1; wordIndex < words.length; wordIndex++) {
+ if (lastLength + words[wordIndex].length >= maxWidth) {
+ result += "\n";
+ lastLength = 0;
+ } else {
+ result += " ";
+ }
+
+ result += words[wordIndex];
+ lastLength += words[wordIndex].length;
}
return result;
diff --git a/tests/issue-content-parser.test.ts b/tests/issue-content-parser.test.ts
index aa3d65d..360c33a 100644
--- a/tests/issue-content-parser.test.ts
+++ b/tests/issue-content-parser.test.ts
@@ -1,5 +1,5 @@
import { IssueContentParser } from "../src/issue-content-parser";
-import { GitHubIssue } from "../src/models";
+import { GitHubIssue, GitHubRepoReference } from "../src/models";
describe("IssueContentParser", () => {
const issueContentParser = new IssueContentParser();
@@ -83,9 +83,11 @@ Test content 2
});
describe("extractIssueDependencies", () => {
+ const repoRef: GitHubRepoReference = { repoOwner: "testOwner", repoName: "testRepo" };
+
it("empty body", () => {
const issue = { body: undefined } as GitHubIssue;
- const actual = issueContentParser.extractIssueDependencies(issue);
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
expect(actual).toEqual([]);
});
@@ -101,7 +103,7 @@ https://github.com/actions/setup-node/issues/4
Test content 3
`,
} as GitHubIssue;
- const actual = issueContentParser.extractIssueDependencies(issue);
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
expect(actual).toEqual([]);
});
@@ -109,7 +111,7 @@ Test content 3
const issue = {
body: "## Hello\nDepends on https://github.com/actions/setup-node/issues/5663\nTest",
} as GitHubIssue;
- const actual = issueContentParser.extractIssueDependencies(issue);
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
expect(actual).toEqual([{ repoOwner: "actions", repoName: "setup-node", issueNumber: 5663 }]);
});
@@ -123,7 +125,7 @@ Depends on https://github.com/actions/setup-node/issues/105, https://github.com/
Test content
`,
} as GitHubIssue;
- const actual = issueContentParser.extractIssueDependencies(issue);
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
expect(actual).toEqual([
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 105 },
{ repoOwner: "actions", repoName: "setup-python", issueNumber: 115 },
@@ -143,7 +145,7 @@ Depends on https://github.com/actions/setup-ruby/issues/105 & https://github.com
Test content
`,
} as GitHubIssue;
- const actual = issueContentParser.extractIssueDependencies(issue);
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
expect(actual).toEqual([
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 101 },
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 102 },
@@ -166,11 +168,36 @@ Dependencies: https://github.com/actions/setup-node/issues/103
Test content
`,
} as GitHubIssue;
- const actual = issueContentParser.extractIssueDependencies(issue);
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
+ expect(actual).toEqual([
+ { repoOwner: "actions", repoName: "setup-node", issueNumber: 101 },
+ { repoOwner: "actions", repoName: "setup-node", issueNumber: 102 },
+ { repoOwner: "actions", repoName: "setup-node", issueNumber: 103 },
+ ]);
+ });
+
+ it("diffent types of issues referencing", () => {
+ const issue = {
+ body: `
+Hello
+
+Depends on https://github.com/actions/setup-node/issues/101
+depends on: https://github.com/actions/setup-node/issues/102
+Dependencies: https://github.com/actions/setup-node/issues/103
+Depends on: #123, #456, https://github.com/actions/setup-node/issues/105, #701
+
+Test content
+`,
+ } as GitHubIssue;
+ const actual = issueContentParser.extractIssueDependencies(issue, repoRef);
expect(actual).toEqual([
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 101 },
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 102 },
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 103 },
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 123 },
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 456 },
+ { repoOwner: "actions", repoName: "setup-node", issueNumber: 105 },
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 701 },
]);
});
});
diff --git a/tests/mermaid-node.test.ts b/tests/mermaid-node.test.ts
index 471366e..0bcb4c1 100644
--- a/tests/mermaid-node.test.ts
+++ b/tests/mermaid-node.test.ts
@@ -55,29 +55,18 @@ describe("MermaidNode", () => {
});
});
- describe("getWrappedTitle", () => {
+ describe("getFormattedTitle", () => {
it.each([
["", ""],
["hello world", "hello world"],
- [
- "Integrate software report diff module into macOS Monterey pipeline and validate deployment pipeline end-to-end",
- "Integrate software report diff module into\nmacOS Monterey pipeline and validate\ndeployment pipeline end-to-end",
- ],
[
"Onboard Linux image generation to new software report module",
"Onboard Linux image generation to new\nsoftware report module",
],
- [
- "Integrate auxiliary release scripts into Windows / Linux deployment pipelines",
- "Integrate auxiliary release scripts into\nWindows / Linux deployment pipelines",
- ],
- [
- "Implement unit tests and e2e tests for software report diff module",
- "Implement unit tests and e2e tests for\nsoftware report diff module",
- ],
+ ['Update link "Learn more" with new link', "Update link 'Learn more' with new link"],
])("case %#", (input: string, expected: string) => {
const node = new MermaidNode("issue", input, "notstarted");
- const actual = node.getWrappedTitle();
+ const actual = node.getFormattedTitle();
expect(actual).toBe(expected);
});
});
diff --git a/tests/utils.test.ts b/tests/utils.test.ts
index a6c65d6..a2d8423 100644
--- a/tests/utils.test.ts
+++ b/tests/utils.test.ts
@@ -1,4 +1,5 @@
-import { parseIssuesUrls, parseIssueUrl } from "../src/utils";
+import { GitHubRepoReference } from "../src/models";
+import { parseIssueNumber, parseIssuesUrls, parseIssueUrl, wrapString } from "../src/utils";
describe("parseIssueUrl", () => {
it("invalid site host", () => {
@@ -46,15 +47,41 @@ describe("parseIssueUrl", () => {
});
});
+describe("parseIssueNumber", () => {
+ const repoRef: GitHubRepoReference = { repoOwner: "testOwner", repoName: "testRepo" };
+
+ it("empty string", () => {
+ const actual = parseIssueNumber("", repoRef);
+ expect(actual).toBeNull();
+ });
+
+ it("invalid format", () => {
+ const actual = parseIssueNumber("#abc", repoRef);
+ expect(actual).toBeNull();
+ });
+
+ it("parses issue number correctly", () => {
+ const actual = parseIssueNumber("#123", repoRef);
+ expect(actual).toStrictEqual({
+ repoOwner: "testOwner",
+ repoName: "testRepo",
+ issueNumber: 123,
+ });
+ });
+});
+
describe("parseIssuesUrls", () => {
+ const repoRef: GitHubRepoReference = { repoOwner: "testOwner", repoName: "testRepo" };
+
it("parses single issue url", () => {
- const actual = parseIssuesUrls("https://github.com/actions/setup-node/issues/5663");
+ const actual = parseIssuesUrls("https://github.com/actions/setup-node/issues/5663", repoRef);
expect(actual).toStrictEqual([{ repoOwner: "actions", repoName: "setup-node", issueNumber: 5663 }]);
});
it("parses multiple issues urls", () => {
const actual = parseIssuesUrls(
- "https://github.com/actions/setup-node/issues/110 https://github.com/actions/setup-go/issues/115 https://github.com/actions/setup-go/issues/120"
+ "https://github.com/actions/setup-node/issues/110 https://github.com/actions/setup-go/issues/115 https://github.com/actions/setup-go/issues/120",
+ repoRef
);
expect(actual).toStrictEqual([
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 110 },
@@ -65,7 +92,8 @@ describe("parseIssuesUrls", () => {
it("parses multiple comma separated issues urls", () => {
const actual = parseIssuesUrls(
- "https://github.com/actions/setup-node/issues/110, https://github.com/actions/setup-go/issues/115"
+ "https://github.com/actions/setup-node/issues/110, https://github.com/actions/setup-go/issues/115",
+ repoRef
);
expect(actual).toStrictEqual([
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 110 },
@@ -75,18 +103,71 @@ describe("parseIssuesUrls", () => {
it("parses multiple issues urls with additional words", () => {
const actual = parseIssuesUrls(
- "Depends on: https://github.com/actions/setup-node/issues/110, https://github.com/actions/setup-go/issues/115"
+ "Depends on: https://github.com/actions/setup-node/issues/110, https://github.com/actions/setup-go/issues/115",
+ repoRef
+ );
+ expect(actual).toStrictEqual([
+ { repoOwner: "actions", repoName: "setup-node", issueNumber: 110 },
+ { repoOwner: "actions", repoName: "setup-go", issueNumber: 115 },
+ ]);
+ });
+
+ it("parses multiple issue numbers with additional words", () => {
+ const actual = parseIssuesUrls("Depends on: #123, gawgaw #213 afaaw", repoRef);
+ expect(actual).toStrictEqual([
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 123 },
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 213 },
+ ]);
+ });
+
+ it("parses multiple issues in different formats", () => {
+ const actual = parseIssuesUrls(
+ "Depends on: https://github.com/actions/setup-node/issues/110, #123, gawgaw #213 afaaw, https://github.com/actions/setup-go/issues/115",
+ repoRef
);
expect(actual).toStrictEqual([
{ repoOwner: "actions", repoName: "setup-node", issueNumber: 110 },
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 123 },
+ { repoOwner: "testOwner", repoName: "testRepo", issueNumber: 213 },
{ repoOwner: "actions", repoName: "setup-go", issueNumber: 115 },
]);
});
it("no valid urls found", () => {
const actual = parseIssuesUrls(
- "https://github.com/actions/setup-node/, https://github.com/actions/setup-go/issues/ https://github.com/actions/setup-go/issues/fake"
+ "https://github.com/actions/setup-node/, https://github.com/actions/setup-go/issues/ https://github.com/actions/setup-go/issues/fake",
+ repoRef
);
expect(actual).toStrictEqual([]);
});
});
+
+describe("wrapString", () => {
+ it.each([
+ ["", 40, ""],
+ ["hello world", 40, "hello world"],
+ [
+ "Integrate software report diff module into macOS Monterey pipeline and validate deployment pipeline end-to-end",
+ 40,
+ "Integrate software report diff module into\nmacOS Monterey pipeline and validate\ndeployment pipeline end-to-end",
+ ],
+ [
+ "Onboard Linux image generation to new software report module",
+ 40,
+ "Onboard Linux image generation to new\nsoftware report module",
+ ],
+ [
+ "Integrate auxiliary release scripts into Windows / Linux deployment pipelines",
+ 40,
+ "Integrate auxiliary release scripts into\nWindows / Linux deployment pipelines",
+ ],
+ [
+ "Implement unit tests and e2e tests for software report diff module",
+ 40,
+ "Implement unit tests and e2e tests for\nsoftware report diff module",
+ ],
+ ])("case %#", (input: string, maxWidth: number, expected: string) => {
+ const actual = wrapString(input, maxWidth);
+ expect(actual).toBe(expected);
+ });
+});