Skip to content

Commit 159e240

Browse files
committed
test on real graph and minor improvements
1 parent 8a2f0a2 commit 159e240

10 files changed

+346
-36
lines changed

action.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ inputs:
55
root-issue-url:
66
description: 'Url of root issue'
77
required: true
8+
access-token:
9+
description: 'GitHub API Token with read and write access to root issue and read access to all tasklist issues'
10+
required: true
11+
section-title:
12+
description: 'Title of markdown header where mermaid chart should be placed'
13+
required: true
14+
includeLegend:
15+
description: 'Set this option to include legend to mermaid diagram'
16+
required: false
17+
dryRun:
18+
description: 'Set this option to not update root issue with updated mermaid diagram and only print new diagram to output'
19+
required: false
820
runs:
921
using: 'node16'
1022
main: 'dist/index.js'

dist/index.js

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,49 @@
22
/******/ var __webpack_modules__ = ({
33

44
/***/ 88:
5-
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
66

77
"use strict";
88

9+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10+
if (k2 === undefined) k2 = k;
11+
var desc = Object.getOwnPropertyDescriptor(m, k);
12+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13+
desc = { enumerable: true, get: function() { return m[k]; } };
14+
}
15+
Object.defineProperty(o, k2, desc);
16+
}) : (function(o, m, k, k2) {
17+
if (k2 === undefined) k2 = k;
18+
o[k2] = m[k];
19+
}));
20+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21+
Object.defineProperty(o, "default", { enumerable: true, value: v });
22+
}) : function(o, v) {
23+
o["default"] = v;
24+
});
25+
var __importStar = (this && this.__importStar) || function (mod) {
26+
if (mod && mod.__esModule) return mod;
27+
var result = {};
28+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29+
__setModuleDefault(result, mod);
30+
return result;
31+
};
932
Object.defineProperty(exports, "__esModule", ({ value: true }));
1033
exports.parseInputs = void 0;
34+
const core = __importStar(__nccwpck_require__(2186));
1135
const utils_1 = __nccwpck_require__(918);
1236
const parseInputs = () => {
13-
const rootIssueUrl = "https://github.com/maxim-lobanov/build-issue-dependencies-graph/issues/1";
37+
const rootIssueUrl = core.getInput("root-issue-url", { required: true });
1438
const rootIssue = (0, utils_1.parseIssueUrl)(rootIssueUrl);
1539
if (!rootIssue) {
1640
throw new Error(`Failed to extract issue details from url '${rootIssueUrl}'`);
1741
}
1842
return {
1943
rootIssue,
20-
accessToken: "",
21-
sectionTitle: "Spec graph",
22-
dryRun: false
44+
sectionTitle: core.getInput("section-title", { required: true }),
45+
includeLegend: core.getBooleanInput('include-legend'),
46+
accessToken: core.getInput("access-token", { required: true }),
47+
dryRun: core.getBooleanInput("dry-run"),
2348
};
2449
};
2550
exports.parseInputs = parseInputs;
@@ -152,7 +177,7 @@ class IssueContentParser {
152177
const contentLines = (_b = (_a = issue.body) === null || _a === void 0 ? void 0 : _a.split("\n")) !== null && _b !== void 0 ? _b : [];
153178
const sectionStartIndex = contentLines.findIndex(x => this.isMarkdownHeaderLine(x, sectionTitle));
154179
if (sectionStartIndex === -1) {
155-
throw `Markdown header '${sectionTitle}' is not found in issue body:\n ${issue.body}`;
180+
throw new Error(`Markdown header '${sectionTitle}' is not found in issue body.`);
156181
}
157182
const sectionEndIndex = contentLines.findIndex((x, index) => index > sectionStartIndex && this.isMarkdownHeaderLine(x));
158183
return [
@@ -176,7 +201,7 @@ class IssueContentParser {
176201
return trimmedLine.toLowerCase() === sectionTitle.toLocaleLowerCase();
177202
}
178203
isTaskListLine(str) {
179-
return str.startsWith("- [ ] ");
204+
return str.startsWith("- [ ] ") || str.startsWith("- [x] ");
180205
}
181206
isDependencyLine(str) {
182207
const dependencyLinePrefixes = ["Dependencies: ", "Predecessors: ", "Depends on ", "Depends on: "];
@@ -230,7 +255,7 @@ const run = async () => {
230255
const config = (0, config_1.parseInputs)();
231256
const githubApiClient = new github_api_client_1.GitHubApiClient(config.accessToken);
232257
const issueContentParser = new issue_content_parser_1.IssueContentParser();
233-
const mermaidRender = new mermaid_render_1.MermaidRender();
258+
const mermaidRender = new mermaid_render_1.MermaidRender(config.includeLegend);
234259
const rootIssue = await githubApiClient.getIssue(config.rootIssue);
235260
const rootIssueTasklist = issueContentParser.extractIssueTasklist(rootIssue);
236261
core.info(`Found ${rootIssueTasklist.length} work items in task list.`);
@@ -256,15 +281,13 @@ const run = async () => {
256281
console.log("Action is run in dry-run mode. Root issue won't be updated");
257282
return;
258283
}
259-
core.info("Updating root issue...");
284+
core.info("Updating root issue content...");
260285
await githubApiClient.updateIssueContent(config.rootIssue, updatedIssueContent);
261286
core.info("Root issue is updated.");
262287
}
263288
catch (error) {
264-
if (error instanceof Error) {
265-
core.setFailed(error.message);
266-
throw error;
267-
}
289+
core.setFailed(error instanceof Error ? error.message : error);
290+
throw error;
268291
}
269292
};
270293
run();
@@ -286,14 +309,32 @@ class MermaidNode {
286309
this.status = status;
287310
this.url = url;
288311
}
312+
getWrappedTitle() {
313+
const maxWidth = 40;
314+
const words = this.title.split(/\s+/);
315+
let result = words[0];
316+
let lastLength = result.length;
317+
for (let wordIndex = 1; wordIndex < words.length; wordIndex++) {
318+
if (lastLength + words[wordIndex].length >= maxWidth) {
319+
result += "\n";
320+
lastLength = 0;
321+
}
322+
else {
323+
result += " ";
324+
}
325+
result += words[wordIndex];
326+
lastLength += words[wordIndex].length;
327+
}
328+
return result;
329+
}
289330
static createFromGitHubIssue(issue) {
290331
return new MermaidNode(`issue${issue.id}`, issue.title, MermaidNode.getNodeStatusFromGitHubIssue(issue), issue.html_url);
291332
}
292333
static getNodeStatusFromGitHubIssue(issue) {
293334
if (issue.state !== "open") {
294335
return "completed";
295336
}
296-
if (issue.assignee !== null) {
337+
if (issue.assignee) {
297338
return "started";
298339
}
299340
return "notstarted";
@@ -318,12 +359,15 @@ exports.MermaidNode = MermaidNode;
318359
Object.defineProperty(exports, "__esModule", ({ value: true }));
319360
exports.MermaidRender = void 0;
320361
class MermaidRender {
362+
constructor(includeLegend) {
363+
this.includeLegend = includeLegend;
364+
}
321365
render(graph) {
322366
return `
367+
${this.renderLegendSection()}
323368
\`\`\`mermaid
324369
flowchart TD
325370
${this.renderCssSection()}
326-
${this.renderLegendSection()}
327371
${this.renderIssuesSection(graph.vertices)}
328372
${this.renderDependencies(graph.edges)}
329373
\`\`\`
@@ -341,7 +385,14 @@ classDef completed fill:#ccffd8,color:#000;
341385
`;
342386
}
343387
renderLegendSection() {
388+
if (!this.includeLegend) {
389+
return "";
390+
}
344391
return `
392+
\`\`\`mermaid
393+
flowchart TD
394+
${this.renderCssSection()}
395+
345396
%% <Legend>
346397

347398
subgraph legend["Legend"]
@@ -353,6 +404,7 @@ subgraph legend["Legend"]
353404
end
354405

355406
%% </Legend>
407+
\`\`\`
356408
`;
357409
}
358410
renderIssuesSection(issues) {
@@ -366,7 +418,7 @@ ${renderedGraphIssues}
366418
`;
367419
}
368420
renderIssue(issue) {
369-
let result = `${issue.nodeId}("${issue.title}"):::${issue.status};`;
421+
let result = `${issue.nodeId}("${issue.getWrappedTitle()}"):::${issue.status};`;
370422
if (issue.url) {
371423
result += `\nclick ${issue.nodeId} href "${issue.url}" _blank;`;
372424
}

src/config.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1+
import * as core from "@actions/core";
12
import { GitHubIssueReference } from "./models";
23
import { parseIssueUrl } from "./utils";
34

45
export interface Config {
56
rootIssue: GitHubIssueReference;
67
sectionTitle: string;
8+
includeLegend: boolean;
79
accessToken: string;
810
dryRun: boolean;
911
}
1012

1113
export const parseInputs = (): Config => {
12-
const rootIssueUrl = "https://github.com/maxim-lobanov/build-issue-dependencies-graph/issues/1";
14+
const rootIssueUrl = core.getInput("root-issue-url", { required: true });
1315
const rootIssue = parseIssueUrl(rootIssueUrl);
1416
if (!rootIssue) {
1517
throw new Error(`Failed to extract issue details from url '${rootIssueUrl}'`);
1618
}
1719

1820
return {
1921
rootIssue,
20-
accessToken: "",
21-
sectionTitle: "Spec graph",
22-
dryRun: false
22+
sectionTitle: core.getInput("section-title", { required: true }),
23+
includeLegend: core.getBooleanInput("include-legend"),
24+
accessToken: core.getInput("access-token", { required: true }),
25+
dryRun: core.getBooleanInput("dry-run"),
2326
};
2427
};

src/issue-content-parser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class IssueContentParser {
2626

2727
const sectionStartIndex = contentLines.findIndex(x => this.isMarkdownHeaderLine(x, sectionTitle));
2828
if (sectionStartIndex === -1) {
29-
throw `Markdown header '${sectionTitle}' is not found in issue body:\n ${issue.body}`;
29+
throw new Error(`Markdown header '${sectionTitle}' is not found in issue body.`);
3030
}
3131

3232
const sectionEndIndex = contentLines.findIndex(
@@ -59,7 +59,7 @@ export class IssueContentParser {
5959
}
6060

6161
public isTaskListLine(str: string): boolean {
62-
return str.startsWith("- [ ] ");
62+
return str.startsWith("- [ ] ") || str.startsWith("- [x] ");
6363
}
6464

6565
public isDependencyLine(str: string): boolean {

src/main.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const run = async (): Promise<void> => {
1111
const config = parseInputs();
1212
const githubApiClient = new GitHubApiClient(config.accessToken);
1313
const issueContentParser = new IssueContentParser();
14-
const mermaidRender = new MermaidRender();
14+
const mermaidRender = new MermaidRender(config.includeLegend);
1515

1616
const rootIssue = await githubApiClient.getIssue(config.rootIssue);
1717
const rootIssueTasklist = issueContentParser.extractIssueTasklist(rootIssue);
@@ -50,14 +50,12 @@ const run = async (): Promise<void> => {
5050
return;
5151
}
5252

53-
core.info("Updating root issue...");
53+
core.info("Updating root issue content...");
5454
await githubApiClient.updateIssueContent(config.rootIssue, updatedIssueContent);
5555
core.info("Root issue is updated.");
56-
} catch (error) {
57-
if (error instanceof Error) {
58-
core.setFailed(error.message);
59-
throw error;
60-
}
56+
} catch (error: unknown) {
57+
core.setFailed(error instanceof Error ? error.message : (error as string));
58+
throw error;
6159
}
6260
};
6361

src/mermaid-node.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ export class MermaidNode {
1010
public readonly url?: string
1111
) {}
1212

13+
public getWrappedTitle(): string {
14+
const maxWidth = 40;
15+
const words = this.title.split(/\s+/);
16+
17+
let result = words[0];
18+
let lastLength = result.length;
19+
for (let wordIndex = 1; wordIndex < words.length; wordIndex++) {
20+
if (lastLength + words[wordIndex].length >= maxWidth) {
21+
result += "\n";
22+
lastLength = 0;
23+
} else {
24+
result += " ";
25+
}
26+
27+
result += words[wordIndex];
28+
lastLength += words[wordIndex].length;
29+
}
30+
31+
return result;
32+
}
33+
1334
public static createFromGitHubIssue(issue: GitHubIssue): MermaidNode {
1435
return new MermaidNode(
1536
`issue${issue.id}`,
@@ -24,7 +45,7 @@ export class MermaidNode {
2445
return "completed";
2546
}
2647

27-
if (issue.assignee !== null) {
48+
if (issue.assignee) {
2849
return "started";
2950
}
3051

src/mermaid-render.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { Graph, GraphEdge } from "./graph-builder";
22
import { MermaidNode } from "./mermaid-node";
33

44
export class MermaidRender {
5+
constructor(private readonly includeLegend: boolean) {}
6+
57
public render(graph: Graph): string {
68
return `
9+
${this.renderLegendSection()}
710
\`\`\`mermaid
811
flowchart TD
912
${this.renderCssSection()}
10-
${this.renderLegendSection()}
1113
${this.renderIssuesSection(graph.vertices)}
1214
${this.renderDependencies(graph.edges)}
1315
\`\`\`
@@ -27,7 +29,15 @@ classDef completed fill:#ccffd8,color:#000;
2729
}
2830

2931
private renderLegendSection(): string {
32+
if (!this.includeLegend) {
33+
return "";
34+
}
35+
3036
return `
37+
\`\`\`mermaid
38+
flowchart TD
39+
${this.renderCssSection()}
40+
3141
%% <Legend>
3242
3343
subgraph legend["Legend"]
@@ -39,6 +49,7 @@ subgraph legend["Legend"]
3949
end
4050
4151
%% </Legend>
52+
\`\`\`
4253
`;
4354
}
4455

@@ -55,7 +66,7 @@ ${renderedGraphIssues}
5566
}
5667

5768
private renderIssue(issue: MermaidNode): string {
58-
let result = `${issue.nodeId}("${issue.title}"):::${issue.status};`;
69+
let result = `${issue.nodeId}("${issue.getWrappedTitle()}"):::${issue.status};`;
5970
if (issue.url) {
6071
result += `\nclick ${issue.nodeId} href "${issue.url}" _blank;`;
6172
}

0 commit comments

Comments
 (0)