Skip to content

Commit e49c333

Browse files
authored
switch to comment-rendering
1 parent dff3f22 commit e49c333

File tree

12 files changed

+201
-84
lines changed

12 files changed

+201
-84
lines changed

.env-template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
REPO_GITHUB_PAT="<github PAT>"
1+
REPO_GITHUB_READ_PAT="<github PAT>"
2+
REPO_GITHUB_WRITE_PAT="<github PAT>"
23
CONFIG_PATH="./testConfig2.json"

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<a href="https://github.com/actions/typescript-action/actions"><img alt="typescript-action status" src="https://github.com/actions/typescript-action/workflows/build-test/badge.svg"></a>
33
</p>
44

5+
*`releases/v2` branch implements the board rendering in a issue comment*
6+
57
# Create a JavaScript Action using TypeScript
68

79
Use this template to bootstrap the creation of a TypeScript action.:rocket:

schemas/IConfig.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
},
4646
"additionalProperties": false,
4747
"required": [
48-
"boardIssue",
4948
"repos"
5049
],
5150
"definitions": {
@@ -116,6 +115,10 @@
116115
"newCardsCutoffDays": {
117116
"description": "How many days should elapse since the beginning of\nthe sprint for a card to be treated as `new`.",
118117
"type": "number"
118+
},
119+
"boardComment": {
120+
"description": "The issue that is used as aggregated board.",
121+
"type": "string"
119122
}
120123
},
121124
"additionalProperties": false,

src/interfaces/IConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export interface IConfig {
44
/**
55
* The issue that is used as aggregated board.
66
*/
7-
boardIssue: string;
7+
boardIssue?: string;
88

99
/**
1010
* List of the repositories/projects to track.

src/interfaces/IParsedComment.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { IParsedIssue } from './IParsedIssue';
2+
3+
export interface IParsedComment extends IParsedIssue {
4+
commentId: number;
5+
}

src/interfaces/IParsedIssue.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { IParsedRepo } from './IParsedRepo';
2-
32
export interface IParsedIssue extends IParsedRepo {
43
issueNumber: number;
54
}

src/interfaces/IProject.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ export interface IProject {
4949
* the sprint for a card to be treated as `new`.
5050
*/
5151
newCardsCutoffDays?: number;
52+
53+
/**
54+
* The issue that is used as aggregated board.
55+
*/
56+
boardComment?: string;
5257
}

src/main.ts

Lines changed: 132 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,35 @@ import { renderProject } from './views/renderProject';
99
import { getProjectData } from './utils/getProjectData';
1010
import { env } from './utils/env';
1111
import { getConfigs, validateConfig } from './config';
12-
import { renderOverview } from './views/renderOverview';
12+
// import { renderOverview } from './views/renderOverview';
1313

1414
import { IConfig } from './interfaces/IConfig';
1515

16-
const TOKEN_NAME = 'REPO_GITHUB_PAT';
16+
const TOKEN_READ_NAME = 'REPO_GITHUB_READ_PAT';
17+
const TOKEN_WRITE_NAME = 'REPO_GITHUB_READ_PAT';
1718
const CONFIG_PATH = 'CONFIG_PATH';
1819

19-
const overwriteBoardIssue = async (
20-
issueContents: string,
21-
config: IConfig,
22-
projectKit: ProjectsOctoKit,
23-
) => {
24-
const { status } = await projectKit.updateBoardIssue(
25-
config.boardIssue,
26-
issueContents,
27-
);
20+
// const overwriteBoardIssue = async (
21+
// issueContents: string,
22+
// config: IConfig,
23+
// projectKit: ProjectsOctoKit,
24+
// ) => {
25+
// const { status } = await projectKit.updateBoardIssue(
26+
// config.boardIssue,
27+
// issueContents,
28+
// );
2829

29-
if (status !== 200) {
30-
throw new Error(`Failed to update the issue ${config.boardIssue}`);
31-
}
30+
// if (status !== 200) {
31+
// throw new Error(`Failed to update the issue ${config.boardIssue}`);
32+
// }
3233

33-
console.log(`Successfully updated the board issue ${config.boardIssue}`);
34-
};
34+
// console.log(`Successfully updated the board issue ${config.boardIssue}`);
35+
// };
3536

36-
const getRegex = (projectId?: number) => {
37-
const regex = /<!--\s*codespaces-board:start\s*-->([\W\w]*)<!--\s*codespaces-board:end\s*-->/gim;
38-
return regex;
39-
};
37+
// const getRegex = (projectId?: number) => {
38+
// const regex = /<!--\s*codespaces-board:start\s*-->([\W\w]*)<!--\s*codespaces-board:end\s*-->/gim;
39+
// return regex;
40+
// };
4041

4142
const wrapIssueText = (text: string, projectId?: number) => {
4243
return [
@@ -48,26 +49,98 @@ const wrapIssueText = (text: string, projectId?: number) => {
4849
].join('\n');
4950
};
5051

51-
const updateBoardIssue = async (
52+
// const updateBoardIssue = async (
53+
// issueContents: string,
54+
// config: IConfig,
55+
// projectKit: ProjectsOctoKit,
56+
// ) => {
57+
// if (!config.isReplaceProjectMarkers) {
58+
// return await overwriteBoardIssue(issueContents, config, projectKit);
59+
// }
60+
61+
// const issue = await projectKit.getBoardIssue(config.boardIssue);
62+
63+
// const { body } = issue;
64+
// const newBody = body.replace(getRegex(), wrapIssueText(issueContents));
65+
66+
// await overwriteBoardIssue(newBody, config, projectKit);
67+
// };
68+
69+
// const processConfigRecord = async (
70+
// config: IConfig,
71+
// projectKit: ProjectsOctoKit,
72+
// ) => {
73+
// console.log('Processing config: \n', JSON.stringify(config, null, 2));
74+
75+
// const validationErrors = validateConfig(config);
76+
// if (validationErrors.length) {
77+
// console.error(
78+
// `\n\nNot valid config for the issue ${config.boardIssue}, skipping.. \n`,
79+
// validationErrors,
80+
// '\n\n',
81+
// );
82+
// return;
83+
// }
84+
85+
// console.log(`- Config schema validation passed.`);
86+
87+
// const repoProjects = await projectKit.getAllProjects(config.repos);
88+
89+
// const projectsWithData = [];
90+
// for (let { repo, projects } of repoProjects) {
91+
// for (let project of projects) {
92+
// console.log(`- Getting Project data for ${project.project.name}.`);
93+
// projectsWithData.push({
94+
// project,
95+
// data: await getProjectData(projectKit, config, project),
96+
// });
97+
// }
98+
99+
// const result = projectsWithData.map(({ project, data }) => {
100+
// return renderProject(data, project, config);
101+
// });
102+
103+
// const issueBody = result.join('\n') + '\n';
104+
// let header;
105+
// if (config.headerFileUrl) {
106+
// header = await projectKit.getBoardHeaderText(config.headerFileUrl);
107+
// }
108+
109+
// let footer;
110+
// if (config.footerFileUrl) {
111+
// footer = await projectKit.getBoardHeaderText(config.footerFileUrl);
112+
// }
113+
114+
// const issueContents = [
115+
// header,
116+
// // render all projects overview
117+
// // renderOverview(config, projectsWithData),
118+
// issueBody,
119+
// footer,
120+
// ].join('\n');
121+
122+
// await updateBoardIssue(issueContents, config, projectKit);
123+
// }
124+
// };
125+
126+
const updateBoardComment = async (
127+
commentUrl: string,
52128
issueContents: string,
53-
config: IConfig,
54129
projectKit: ProjectsOctoKit,
55130
) => {
56-
if (!config.isReplaceProjectMarkers) {
57-
return await overwriteBoardIssue(issueContents, config, projectKit);
58-
}
131+
// if (!config.isReplaceProjectMarkers) {
132+
// return await overwriteBoardIssue(issueContents, config, projectKit);
133+
// }
59134

60-
const issue = await projectKit.getBoardIssue(config.boardIssue);
135+
console.log(`>> updating the comment ${commentUrl} with contents length: ${issueContents.length}`);
61136

62-
const { body } = issue;
63-
const newBody = body.replace(getRegex(), wrapIssueText(issueContents));
64-
65-
await overwriteBoardIssue(newBody, config, projectKit);
137+
return await projectKit.updateBoardComment(commentUrl, issueContents);
66138
};
67139

68-
const processConfigRecord = async (
140+
const processConfigRecordComment = async (
69141
config: IConfig,
70-
projectKit: ProjectsOctoKit,
142+
readProjectKit: ProjectsOctoKit,
143+
writeProjectKit: ProjectsOctoKit,
71144
) => {
72145
console.log('Processing config: \n', JSON.stringify(config, null, 2));
73146

@@ -81,59 +154,57 @@ const processConfigRecord = async (
81154
return;
82155
}
83156

84-
console.log(`- Config schema validation passed.`);
157+
console.log('\n- Config schema validation passed.\n');
85158

86-
const repoProjects = await projectKit.getAllProjects(config.repos);
159+
const repoProjects = await readProjectKit.getAllProjects(config.repos);
87160

88161
const projectsWithData = [];
89162
for (let { repo, projects } of repoProjects) {
90163
for (let project of projects) {
91164
console.log(`- Getting Project data for ${project.project.name}.`);
92165
projectsWithData.push({
93166
project,
94-
data: await getProjectData(projectKit, config, project),
167+
data: await getProjectData(readProjectKit, config, project),
95168
});
96169
}
97170

98-
const result = projectsWithData.map(({ project, data }) => {
99-
return renderProject(data, project, config);
100-
});
171+
for (let { project, data } of projectsWithData) {
172+
const comment = renderProject(data, project, config);
173+
const { projectConfig } = project;
174+
if (typeof projectConfig === 'number') {
175+
continue;
176+
}
101177

102-
const issueBody = result.join('\n') + '\n';
103-
let header;
104-
if (config.headerFileUrl) {
105-
header = await projectKit.getBoardHeaderText(config.headerFileUrl);
106-
}
178+
const { boardComment } = projectConfig;
179+
if (!boardComment) {
180+
continue;
181+
}
107182

108-
let footer;
109-
if (config.footerFileUrl) {
110-
footer = await projectKit.getBoardHeaderText(config.footerFileUrl);
183+
await updateBoardComment(boardComment, comment, writeProjectKit);
111184
}
112-
113-
const issueContents = [
114-
header,
115-
// render all projects overview
116-
// renderOverview(config, projectsWithData),
117-
issueBody,
118-
footer,
119-
].join('\n');
120-
121-
await updateBoardIssue(issueContents, config, projectKit);
122185
}
123186
};
124187

125188
async function run(): Promise<void> {
126189
try {
127-
const token = env(TOKEN_NAME) ?? core.getInput('token');
190+
const token = core.getInput('token');
191+
const readToken = env(TOKEN_READ_NAME) ?? core.getInput('readToken') ?? token;
192+
const writeToken = env(TOKEN_READ_NAME) ?? core.getInput('writeToken') ?? token;
193+
194+
if (!readToken) {
195+
throw new AuthorizationError('No read token found.');
196+
}
128197

129-
if (!token) {
130-
throw new AuthorizationError('No token found.');
198+
if (!writeToken) {
199+
throw new AuthorizationError('No write token found.');
131200
}
132201

133-
const projectKit = new ProjectsOctoKit(token);
202+
const readProjectKit = new ProjectsOctoKit(readToken);
203+
const writeProjectKit = new ProjectsOctoKit(writeToken);
204+
134205
const configsFilePath = env(CONFIG_PATH) ?? core.getInput('config');
135206
for (let config of getConfigs(configsFilePath)) {
136-
await processConfigRecord(config, projectKit);
207+
await processConfigRecordComment(config, readProjectKit, writeProjectKit);
137208
}
138209
} catch (error) {
139210
console.error(error);

src/octokit/ProjectsOctoKit.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { OctoKitBase } from './OctoKitBase';
2-
import { parseIssueApiUrl, parseIssueUrl } from '../utils/parseIssueUrl';
2+
import { parseCommentUrl, parseIssueApiUrl, parseIssueUrl } from '../utils/parseIssueUrl';
33
import { parseFileUrl } from '../utils/parseFileUrl';
44
import { notEmpty } from '../utils/functional/notEmpty';
55

@@ -299,7 +299,6 @@ export class ProjectsOctoKit extends OctoKitBase {
299299
}
300300

301301
const { owner, repo, issueNumber } = issue;
302-
303302
return await this.kit.issues.update({
304303
owner,
305304
repo,
@@ -330,6 +329,29 @@ export class ProjectsOctoKit extends OctoKitBase {
330329
return data;
331330
};
332331

332+
public updateBoardComment = async (
333+
commentUrl: string,
334+
body: string,
335+
) => {
336+
const comment = parseCommentUrl(commentUrl);
337+
if (!comment) {
338+
throw new Error(`cannot parse the comment ${commentUrl}`);
339+
}
340+
341+
const { owner, repo, issueNumber, commentId } = comment;
342+
const { status } = await this.kit.issues.updateComment({
343+
owner,
344+
repo,
345+
issue_number: issueNumber,
346+
comment_id: commentId,
347+
body,
348+
});
349+
350+
if (status !== 200) {
351+
throw new Error(`Failed to update the comment ${commentUrl}`);
352+
}
353+
};
354+
333355
public getBoardHeaderText = async (fileUrl: string): Promise<string> => {
334356
const fileRef = parseFileUrl(fileUrl);
335357

src/utils/parseIssueUrl.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { IParsedComment } from '../interfaces/IParsedComment';
12
import { IParsedIssue } from '../interfaces/IParsedIssue';
23

34
export const parseIssueUrl = (issueUrl: string): IParsedIssue | null => {
@@ -17,6 +18,24 @@ export const parseIssueUrl = (issueUrl: string): IParsedIssue | null => {
1718
};
1819
};
1920

21+
export const parseCommentUrl = (issueUrl: string): IParsedComment | null => {
22+
const uri = new URL(issueUrl);
23+
const { pathname, hash } = uri;
24+
25+
if (uri.hostname !== 'github.com') {
26+
return null;
27+
}
28+
29+
const split = pathname.split('/');
30+
31+
return {
32+
owner: split[1],
33+
repo: split[2],
34+
issueNumber: parseInt(split[4], 10),
35+
commentId: parseInt(hash.split('#issuecomment-')[1], 10),
36+
};
37+
};
38+
2039

2140
export const parseIssueApiUrl = (issueUrl: string): IParsedIssue => {
2241
const uri = new URL(issueUrl);

0 commit comments

Comments
 (0)