Skip to content

Commit 987ed46

Browse files
authored
add todo
1 parent 081f0c6 commit 987ed46

14 files changed

+405
-124
lines changed

TODO.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- Pass the config JSON file refrence thru the yml file
2+
- Create the JSON schema for config
3+
- Add the PR parsing logic
4+
- Add developer achievements
5+
- stats on when moved from one column to another

src/interfaces/IConfig.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import { IRepoSourceConfig } from './IRepoSourceConfig';
22

33
export interface IConfig {
4+
// issue that is used as board
45
boardIssue: string;
6+
// yyyy/mm/dd => 2020/10/15
7+
sprintStartDate?: string;
8+
// number in days with weekends, e.g. 3 weeks => 21
9+
sprintDuration?: number;
10+
// number of holidays, default: 0
11+
sprintNumberHolidays?: number;
12+
// file that will be added as the issue header
513
headerFileUrl?: string;
14+
// file that will be added as the issue footer
615
footerFileUrl?: string;
16+
// list of the repos to track
717
repos: IRepoSourceConfig[];
818
}

src/interfaces/IProjectData.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { IWrappedIssue } from './IWrappedIssue';
2+
3+
export interface IProjectData {
4+
inWorkIssues: IWrappedIssue[];
5+
doneOrDeployIssues: IWrappedIssue[];
6+
allPlannedIssues: IWrappedIssue[];
7+
// raw:
8+
backlogIssues: IWrappedIssue[];
9+
committedIssues: IWrappedIssue[];
10+
progressIssues: IWrappedIssue[];
11+
inReviewIssues: IWrappedIssue[];
12+
blockedIssues: IWrappedIssue[];
13+
waitingToDeployIssues: IWrappedIssue[];
14+
doneIssues: IWrappedIssue[];
15+
}
16+
17+

src/interfaces/IProjectStats.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface IProjectStats {
2+
doneRate: number;
3+
inWorkRate: number;
4+
committedRate: number;
5+
}

src/main.ts

Lines changed: 26 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -7,118 +7,17 @@ require('dotenv').config();
77
import { AuthorizationError } from './errors/AuthorizationError';
88
import { ProjectsOctoKit } from './octokit/ProjectsOctoKit';
99
import { TEST_CONFIG } from './testConfig';
10-
import { TColumnTypes } from './interfaces/TColumnTypes';
1110
import { IWrappedIssue } from './interfaces/IWrappedIssue';
12-
import { renderIssuesBlock } from './views/renderIssuesBlock';
1311
import { TRepoIssue } from './interfaces/TRepoIssue';
14-
import { TProject } from './interfaces/TProject';
15-
import { IRepoSourceConfig } from './interfaces/IRepoSourceConfig';
12+
import { renderProject } from './views/renderProject';
13+
import { renderDaysLeft } from './views/renderDaysLeft';
14+
import { renderOverview } from './views/renderOverview';
15+
import { getProjectData } from './utils/getProjectData';
1616

1717
export const OWNER = 'legomushroom';
1818
export const REPO = 'codespaces-board';
1919
const TOKEN_NAME = 'REPO_GITHUB_PAT';
2020

21-
const renderProject = async (
22-
projectKit: ProjectsOctoKit,
23-
repo: IRepoSourceConfig,
24-
project: TProject,
25-
): Promise<string> => {
26-
const columns = await projectKit.getColumns(project);
27-
const issues = await projectKit.getRepoIssues(repo);
28-
29-
const backlogIssues = await projectKit.filterIssuesForColumnCards(
30-
issues,
31-
columns,
32-
TColumnTypes.Backlog,
33-
);
34-
35-
const committedIssues = await projectKit.filterIssuesForColumnCards(
36-
issues,
37-
columns,
38-
TColumnTypes.Committed,
39-
);
40-
41-
const blockedIssues = await projectKit.filterIssuesForColumnCards(
42-
issues,
43-
columns,
44-
TColumnTypes.Blocked,
45-
);
46-
47-
const progressIssues = await projectKit.filterIssuesForColumnCards(
48-
issues,
49-
columns,
50-
TColumnTypes.InProgress,
51-
);
52-
53-
const inReviewIssues = await projectKit.filterIssuesForColumnCards(
54-
issues,
55-
columns,
56-
TColumnTypes.InReview,
57-
);
58-
59-
const waitingToDeployIssues = await projectKit.filterIssuesForColumnCards(
60-
issues,
61-
columns,
62-
TColumnTypes.WaitingToDeploy,
63-
);
64-
65-
const doneIssues = await projectKit.filterIssuesForColumnCards(
66-
issues,
67-
columns,
68-
TColumnTypes.Done,
69-
);
70-
71-
const inWorkIssues = [...progressIssues, ...inReviewIssues];
72-
const doneOrDeployIssues = [...waitingToDeployIssues, ...doneIssues];
73-
const allPlannedIssues = [...blockedIssues, ...committedIssues, ...inWorkIssues, ...doneOrDeployIssues];
74-
75-
const doneRate = doneOrDeployIssues.length / allPlannedIssues.length;
76-
const inWorkRate = inWorkIssues.length / allPlannedIssues.length;
77-
const committedRate = committedIssues.length / allPlannedIssues.length;
78-
79-
const donePercent = Math.round(100 * doneRate);
80-
const inWorkPercent = Math.round(100 * inWorkRate);
81-
const committedPercent = Math.round(100 * committedRate);
82-
83-
const blockedIssuesString = renderIssuesBlock(
84-
`⚠️ ${blockedIssues.length} Blocked`,
85-
blockedIssues,
86-
false,
87-
);
88-
89-
const inWorkCount = `${inWorkIssues.length}/${allPlannedIssues.length}`;
90-
const inWorkIssuesString = renderIssuesBlock(
91-
`🏃 ${inWorkCount} In work (${inWorkPercent}%)`,
92-
inWorkIssues,
93-
);
94-
95-
const committedIssuesString = renderIssuesBlock(
96-
`💪 ${committedIssues.length} Committed (${committedPercent}%)`,
97-
committedIssues,
98-
);
99-
100-
const doneCount = `${doneOrDeployIssues.length}/${allPlannedIssues.length}`;
101-
const doneIssuesString = renderIssuesBlock(
102-
`✅ ${doneCount} Done (${donePercent}%)`,
103-
doneOrDeployIssues,
104-
);
105-
106-
const projectTitle = `## ${project.name} - ${donePercent}% done`;
107-
const projectLink = `Link: [${project.name}](${project.html_url})`;
108-
const backlogIssuesCountString = `\n*Backlog: ${backlogIssues.length} issues*`
109-
110-
return [
111-
'',
112-
projectTitle,
113-
projectLink,
114-
blockedIssuesString,
115-
committedIssuesString,
116-
inWorkIssuesString,
117-
doneIssuesString,
118-
backlogIssuesCountString,
119-
].join('\n');
120-
};
121-
12221
async function run(): Promise<void> {
12322
try {
12423
const token = env(TOKEN_NAME) ?? core.getInput('token');
@@ -131,14 +30,22 @@ async function run(): Promise<void> {
13130
const repoProjects = await projectKit.getAllProjects(TEST_CONFIG.repos);
13231

13332
for (let { repo, projects } of repoProjects) {
134-
const result = await Promise.all(
135-
projects.map((project) => {
136-
return renderProject(projectKit, repo, project);
33+
const projectsWithData = await Promise.all(
34+
projects.map(async (project) => {
35+
const data = await getProjectData(projectKit, repo, project);
36+
return {
37+
project,
38+
data
39+
};
13740
}),
13841
);
13942

140-
const issueBody = result.join('\n') + '\n';
43+
const result =
44+
projectsWithData.map(({ project, data }) => {
45+
return renderProject(data, project);
46+
});
14147

48+
const issueBody = result.join('\n') + '\n';
14249
let header;
14350
if (TEST_CONFIG.headerFileUrl) {
14451
header = await projectKit.getBoardHeaderText(TEST_CONFIG.headerFileUrl);
@@ -149,7 +56,16 @@ async function run(): Promise<void> {
14956
footer = await projectKit.getBoardHeaderText(TEST_CONFIG.footerFileUrl);
15057
}
15158

152-
const issueContents = [header, issueBody, footer].join('\n');
59+
const projectsData = projectsWithData.map((x) => {
60+
return x.data;
61+
});
62+
63+
const issueContents = [
64+
header,
65+
renderOverview(TEST_CONFIG, projectsWithData),
66+
issueBody,
67+
footer
68+
].join('\n');
15369

15470
const { status } = await projectKit.updateBoardIssue(
15571
TEST_CONFIG.boardIssue,

src/testConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { IConfig } from './interfaces/IConfig';
22

33
export const TEST_CONFIG: IConfig = {
4+
sprintStartDate: '2020-10-15',
5+
sprintDuration: 21,
6+
sprintNumberHolidays: 2,
47
boardIssue: 'https://github.com/legomushroom/codespaces-board/issues/12',
58
headerFileUrl: 'https://raw.githubusercontent.com/legomushroom/codespaces-board/main/sprints/sprint%2012/header.md',
69
footerFileUrl: 'https://raw.githubusercontent.com/legomushroom/codespaces-board/main/sprints/sprint%2012/footer.md',

src/utils/getProjectData.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ProjectsOctoKit } from '../octokit/ProjectsOctoKit';
2+
import { TColumnTypes } from '../interfaces/TColumnTypes';
3+
import { TProject } from '../interfaces/TProject';
4+
import { IRepoSourceConfig } from '../interfaces/IRepoSourceConfig';
5+
import { IProjectData } from '../interfaces/IProjectData';
6+
7+
export const getProjectData = async (
8+
projectKit: ProjectsOctoKit,
9+
repo: IRepoSourceConfig,
10+
project: TProject
11+
): Promise<IProjectData> => {
12+
const columns = await projectKit.getColumns(project);
13+
const issues = await projectKit.getRepoIssues(repo);
14+
15+
const backlogIssues = await projectKit.filterIssuesForColumnCards(
16+
issues,
17+
columns,
18+
TColumnTypes.Backlog
19+
);
20+
21+
const committedIssues = await projectKit.filterIssuesForColumnCards(
22+
issues,
23+
columns,
24+
TColumnTypes.Committed
25+
);
26+
27+
const blockedIssues = await projectKit.filterIssuesForColumnCards(
28+
issues,
29+
columns,
30+
TColumnTypes.Blocked
31+
);
32+
33+
const progressIssues = await projectKit.filterIssuesForColumnCards(
34+
issues,
35+
columns,
36+
TColumnTypes.InProgress
37+
);
38+
39+
const inReviewIssues = await projectKit.filterIssuesForColumnCards(
40+
issues,
41+
columns,
42+
TColumnTypes.InReview
43+
);
44+
45+
const waitingToDeployIssues = await projectKit.filterIssuesForColumnCards(
46+
issues,
47+
columns,
48+
TColumnTypes.WaitingToDeploy
49+
);
50+
51+
const doneIssues = await projectKit.filterIssuesForColumnCards(
52+
issues,
53+
columns,
54+
TColumnTypes.Done
55+
);
56+
57+
const inWorkIssues = [...progressIssues, ...inReviewIssues];
58+
const doneOrDeployIssues = [...waitingToDeployIssues, ...doneIssues];
59+
const allPlannedIssues = [...blockedIssues, ...committedIssues, ...inWorkIssues, ...doneOrDeployIssues];
60+
61+
return {
62+
// combined
63+
inWorkIssues,
64+
doneOrDeployIssues,
65+
allPlannedIssues,
66+
// plain
67+
backlogIssues,
68+
committedIssues,
69+
progressIssues,
70+
inReviewIssues,
71+
blockedIssues,
72+
waitingToDeployIssues,
73+
doneIssues,
74+
};
75+
};

src/utils/getProjectStats.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { IProjectData } from '../interfaces/IProjectData';
2+
import { IProjectStats } from '../interfaces/IProjectStats';
3+
4+
export const getProjectStats = (data: IProjectData): IProjectStats => {
5+
const {
6+
// combined
7+
inWorkIssues,
8+
doneOrDeployIssues,
9+
allPlannedIssues,
10+
// plain
11+
committedIssues,
12+
} = data;
13+
14+
const doneRate = doneOrDeployIssues.length / allPlannedIssues.length;
15+
const inWorkRate = inWorkIssues.length / allPlannedIssues.length;
16+
const committedRate = committedIssues.length / allPlannedIssues.length;
17+
18+
return {
19+
doneRate,
20+
inWorkRate,
21+
committedRate,
22+
};
23+
};

src/utils/rateToPercent.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const rateToPercent = (rate: number): string => {
2+
return `${Math.round(100 * rate)}%`;
3+
};

src/views/renderDaysLeft.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { IConfig } from '../interfaces/IConfig';
2+
3+
const getBusinessDatesCount = (startDate: Date, endDate: Date) => {
4+
var count = 0;
5+
var curDate = startDate;
6+
7+
while (curDate <= endDate) {
8+
var dayOfWeek = curDate.getDay();
9+
if (!(dayOfWeek == 6 || dayOfWeek == 0)) count++;
10+
curDate.setDate(curDate.getDate() + 1);
11+
}
12+
13+
return count;
14+
};
15+
16+
const daysLeftToEmoj = (daysLeft: number, totalDays: number) => {
17+
const ratio = daysLeft / totalDays;
18+
19+
if (ratio < 0.2) {
20+
return '🔥🔥🔥';
21+
}
22+
23+
if (ratio < 0.3) {
24+
return '🔥🔥';
25+
}
26+
27+
if (ratio < 0.4) {
28+
return '🔥';
29+
}
30+
31+
return '';
32+
};
33+
34+
export const renderDaysLeft = (config: IConfig) => {
35+
const {
36+
sprintStartDate: sprintStartDateString,
37+
sprintDuration,
38+
sprintNumberHolidays = 0,
39+
} = config;
40+
41+
if (!sprintStartDateString || !sprintDuration) {
42+
return undefined;
43+
}
44+
45+
const sprintStartDate = new Date(sprintStartDateString);
46+
const sprintEndDate = new Date(
47+
new Date().setDate(sprintStartDate.getDate() + sprintDuration),
48+
);
49+
50+
const totalBusinessDays =
51+
getBusinessDatesCount(sprintStartDate, sprintEndDate) -
52+
sprintNumberHolidays;
53+
54+
const businessDaysLeftRaw =
55+
getBusinessDatesCount(new Date(), sprintEndDate) - 1 - sprintNumberHolidays;
56+
57+
const businessDaysLeft = Math.max(businessDaysLeftRaw, 0);
58+
59+
return `- ${daysLeftToEmoj(
60+
businessDaysLeft,
61+
totalBusinessDays,
62+
)} **${businessDaysLeft}** work days left`;
63+
};

0 commit comments

Comments
 (0)