@beauraines/sprint-tracker 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +16 -0
- package/.github/dependabot.yml +21 -0
- package/CHANGELOG.md +52 -0
- package/R/HeloSprintBurndown.R +138 -0
- package/R/ProjectHealth.R +109 -0
- package/R/backlogByTeam.R +52 -0
- package/R/backlogHealth.R +163 -0
- package/R/burndDownChart.R +144 -0
- package/R/carryOverAdjustedVelocity.R +154 -0
- package/R/featureTeamBacklog.R +73 -0
- package/R/projectHealth.Rmd +111 -0
- package/R/sprintOutcomes.R +142 -0
- package/R/sprintOutcomesCli.R +103 -0
- package/R/sprintOutcomesFeatureTeam.R +143 -0
- package/R/timeInCodeReview.R +157 -0
- package/README.md +181 -0
- package/cli.js +55 -0
- package/cmds/addOutcomes.js +98 -0
- package/cmds/addProject.js +53 -0
- package/cmds/addSprint.js +117 -0
- package/cmds/config.js +33 -0
- package/cmds/getSprintDetails.js +86 -0
- package/cmds/visualizations.js +116 -0
- package/docker-compose.yml +11 -0
- package/migrations/20230222011333-create-health-table.sql +10 -0
- package/migrations/20230222011334-outcomes.sql +42 -0
- package/migrations/20231206093700-seed-outcomes-table.sql +9 -0
- package/migrations.js +20 -0
- package/package.json +50 -0
- package/scripts/generateBacklogByTeam.sh +20 -0
- package/scripts/generateBacklogHealth.sh +12 -0
- package/scripts/generateChart.sh +7 -0
- package/scripts/generateFeatureTeamBacklog.sh +16 -0
- package/scripts/generateSprintOutcomePlot.sh +12 -0
- package/scripts/sprintOutcomes.sh +29 -0
- package/sql/bugsOpenedDuringSprint.sql +14 -0
- package/sql/bugsOpenedDuringSprintFeature.sql +14 -0
- package/sql/capacityAdjustedVelocity.sql +48 -0
- package/sql/carryOver.sql +33 -0
- package/sql/commitment.sql +30 -0
- package/sql/commitmentMet.sql +60 -0
- package/sql/completed.sql +40 -0
- package/sql/descoped.sql +21 -0
- package/sql/pullForward.sql +19 -0
- package/sql/pullForwardFeature.sql +10 -0
- package/sql/sprintOutcomes.sql +25 -0
- package/sql/sprint_outcomes.sql +23 -0
- package/src/addOutcomes.js +94 -0
- package/src/addSprint.js +108 -0
- package/src/getSprintDetails.js +88 -0
- package/utils/display.js +68 -0
- package/utils/input.js +24 -0
- package/utils/readConfig.js +74 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const prompt = require('prompt-sync')({sigint:true});
|
|
4
|
+
const {homedir} = require('os');
|
|
5
|
+
const { database: db } = require('helpers')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
// TODO read from a config or CLI
|
|
10
|
+
let database = `${homedir}/projects/sprint-tracker/tracker.db`
|
|
11
|
+
|
|
12
|
+
// list projects
|
|
13
|
+
|
|
14
|
+
let statement = 'select * from projects;';
|
|
15
|
+
|
|
16
|
+
let projects = await db.query(database,statement)
|
|
17
|
+
|
|
18
|
+
for (const project of projects) {
|
|
19
|
+
console.log(project.id, project.name)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Select the project
|
|
23
|
+
let project_id
|
|
24
|
+
|
|
25
|
+
let text = 'Enter the project id: '
|
|
26
|
+
while (!project_id) {
|
|
27
|
+
project_id = prompt(text)
|
|
28
|
+
|
|
29
|
+
// validate the response
|
|
30
|
+
if (!projects.find(x => (x.id == project_id))) {
|
|
31
|
+
console.error('Invalid project id')
|
|
32
|
+
project_id = null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
let sprints = await db.query(database,`select id, sprint_name AS name from sprints where project_id = ${project_id};`)
|
|
38
|
+
|
|
39
|
+
// FIXME - sprint_name isn't being displayed because the function is looking for name
|
|
40
|
+
console.log('If your sprint is not displayed, you will have to manually add it.')
|
|
41
|
+
let sprintId = promptForInput(sprints,'Enter the sprint id: ')
|
|
42
|
+
|
|
43
|
+
statement = `select
|
|
44
|
+
p.name,
|
|
45
|
+
s.sprint_name,
|
|
46
|
+
s.start_date,
|
|
47
|
+
s.end_date,
|
|
48
|
+
o.name AS outcome_name,
|
|
49
|
+
so.issue_count,
|
|
50
|
+
so.story_points,
|
|
51
|
+
so.notes,
|
|
52
|
+
o.description
|
|
53
|
+
|
|
54
|
+
from
|
|
55
|
+
sprint_outcomes so
|
|
56
|
+
JOIN sprints s on s.id = so.sprint_id
|
|
57
|
+
JOIN outcomes o on o.id = so.outcome_id
|
|
58
|
+
JOIN projects p on p.id = s.project_id
|
|
59
|
+
where
|
|
60
|
+
-- p.id = 4
|
|
61
|
+
-- and sprint_name = 'ACDC 15'
|
|
62
|
+
p.id = ${project_id}
|
|
63
|
+
and s.id = ${sprintId}`
|
|
64
|
+
|
|
65
|
+
let sprintDetails = await db.query(database,statement)
|
|
66
|
+
console.log(sprintDetails)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main()
|
|
72
|
+
|
|
73
|
+
function promptForInput(items,promptText) {
|
|
74
|
+
let id
|
|
75
|
+
for (const item of items) {
|
|
76
|
+
console.log(item.id, item.name)
|
|
77
|
+
}
|
|
78
|
+
while (!id) {
|
|
79
|
+
id = prompt(promptText)
|
|
80
|
+
|
|
81
|
+
// validate the response
|
|
82
|
+
if (!items.find(x => (x.id == id))) {
|
|
83
|
+
console.error('Invalid id')
|
|
84
|
+
id = null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return id
|
|
88
|
+
}
|
package/utils/display.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const Table = require('cli-table3');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Displays the sprint outcomes. A sprint outcome is a
|
|
5
|
+
*
|
|
6
|
+
* { sprint_id, outcome_id, issue_count, story_points, notes }
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* @param {Array} outcomes the array of sprint outcome objects
|
|
10
|
+
*/
|
|
11
|
+
function sprintOutcomes(outcomes) {
|
|
12
|
+
let table = new Table({
|
|
13
|
+
head: ['Sprint ID', 'Sprint Name','Outcome Name','Issue Count','Story Points','Notes']
|
|
14
|
+
});
|
|
15
|
+
for (const outcome of outcomes) {
|
|
16
|
+
table.push([
|
|
17
|
+
outcome.sprint_id,
|
|
18
|
+
outcome.sprint_name,
|
|
19
|
+
outcome.outcome_name,
|
|
20
|
+
outcome.issue_count,
|
|
21
|
+
outcome.story_points,
|
|
22
|
+
outcome.notes
|
|
23
|
+
])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(table.toString());
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sprintDetails(details) {
|
|
32
|
+
let table = new Table({
|
|
33
|
+
head: ['Name', 'Sprint Name','Start Date','End Date', 'Outcome', 'Issue Count','Story Points','Notes']
|
|
34
|
+
})
|
|
35
|
+
for (const detail of details) {
|
|
36
|
+
table.push([
|
|
37
|
+
detail.name,
|
|
38
|
+
detail.sprint_name,
|
|
39
|
+
detail.start_date,
|
|
40
|
+
detail.end_date,
|
|
41
|
+
detail.outcome_name,
|
|
42
|
+
detail.issue_count,
|
|
43
|
+
detail.story_points,
|
|
44
|
+
detail.notes
|
|
45
|
+
])
|
|
46
|
+
}
|
|
47
|
+
console.log(table.toString());
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function project(project) {
|
|
52
|
+
let table = new Table({})
|
|
53
|
+
for (const key in project) {
|
|
54
|
+
if (Object.hasOwnProperty.call(project, key)) {
|
|
55
|
+
const element = project[key];
|
|
56
|
+
table.push({
|
|
57
|
+
[key]: element
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log(table.toString());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
project,
|
|
66
|
+
sprintDetails,
|
|
67
|
+
sprintOutcomes
|
|
68
|
+
}
|
package/utils/input.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const prompt = require('prompt-sync')({sigint:true});
|
|
2
|
+
|
|
3
|
+
function promptForInput(items,promptText) {
|
|
4
|
+
let id
|
|
5
|
+
for (const item of items) {
|
|
6
|
+
console.log(item.id, item.name)
|
|
7
|
+
}
|
|
8
|
+
while (!id) {
|
|
9
|
+
id = prompt(promptText)
|
|
10
|
+
|
|
11
|
+
// validate the response
|
|
12
|
+
if (!items.find(x => (x.id == id))) {
|
|
13
|
+
console.error('Invalid id')
|
|
14
|
+
id = null
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return id
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
prompt,
|
|
23
|
+
promptForInput
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { helpers, database } = require('helpers')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads the specified config file
|
|
6
|
+
*
|
|
7
|
+
* @param {string} configFile the fully qualified path and file name for the config file
|
|
8
|
+
* @returns {object} the configuration object
|
|
9
|
+
*/
|
|
10
|
+
readConfig = async (configFile) => {
|
|
11
|
+
|
|
12
|
+
let config
|
|
13
|
+
if ( await helpers.fileExists(configFile) ) {
|
|
14
|
+
config = fs.readFileSync(configFile,
|
|
15
|
+
{ encoding: 'utf8', flag: 'r' });
|
|
16
|
+
config = JSON.parse(config);
|
|
17
|
+
} else {
|
|
18
|
+
console.error(`Config file not found. You must create one using the config command`)
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return config
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validates the config file to ensure that it has all of the required properties specified. This
|
|
27
|
+
* is only schema validation, it does not check if the properties are valid values or data types.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} configFile the fully qualified path and file name for the config file
|
|
30
|
+
* @param {Array} configProps an array of properties that defines a valid config file
|
|
31
|
+
*
|
|
32
|
+
* @throws {Configuration file not found}
|
|
33
|
+
* @throws {Invalid configuration file}
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
validateConfig = async (configFile, configProps) => {
|
|
37
|
+
if (! fs.existsSync(configFile) ) {
|
|
38
|
+
throw new Error('Configuration file not found')
|
|
39
|
+
}
|
|
40
|
+
let config
|
|
41
|
+
config = fs.readFileSync(configFile,
|
|
42
|
+
{ encoding: 'utf8', flag: 'r' });
|
|
43
|
+
config = JSON.parse(config);
|
|
44
|
+
// Check for properties
|
|
45
|
+
let validConfig = true
|
|
46
|
+
for (key of configProps) {
|
|
47
|
+
validConfig = config[key] ? true : false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!validConfig) {
|
|
51
|
+
throw Error('Invalid configuration file')
|
|
52
|
+
}
|
|
53
|
+
return validConfig
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a boilerplate config file
|
|
58
|
+
*
|
|
59
|
+
* @param {string} configFile the fully qualified path and file name for the config file
|
|
60
|
+
* @param {Array} configProps an array of properties that defines a valid config file
|
|
61
|
+
*
|
|
62
|
+
*/
|
|
63
|
+
createConfig = async (configFile,configProps) => {
|
|
64
|
+
let config = {}
|
|
65
|
+
for (key of configProps) {
|
|
66
|
+
config[key] = ''
|
|
67
|
+
}
|
|
68
|
+
fs.writeFileSync(configFile,JSON.stringify(config))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
readConfig,
|
|
73
|
+
createConfig
|
|
74
|
+
}
|