@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.
Files changed (53) hide show
  1. package/.eslintrc.json +16 -0
  2. package/.github/dependabot.yml +21 -0
  3. package/CHANGELOG.md +52 -0
  4. package/R/HeloSprintBurndown.R +138 -0
  5. package/R/ProjectHealth.R +109 -0
  6. package/R/backlogByTeam.R +52 -0
  7. package/R/backlogHealth.R +163 -0
  8. package/R/burndDownChart.R +144 -0
  9. package/R/carryOverAdjustedVelocity.R +154 -0
  10. package/R/featureTeamBacklog.R +73 -0
  11. package/R/projectHealth.Rmd +111 -0
  12. package/R/sprintOutcomes.R +142 -0
  13. package/R/sprintOutcomesCli.R +103 -0
  14. package/R/sprintOutcomesFeatureTeam.R +143 -0
  15. package/R/timeInCodeReview.R +157 -0
  16. package/README.md +181 -0
  17. package/cli.js +55 -0
  18. package/cmds/addOutcomes.js +98 -0
  19. package/cmds/addProject.js +53 -0
  20. package/cmds/addSprint.js +117 -0
  21. package/cmds/config.js +33 -0
  22. package/cmds/getSprintDetails.js +86 -0
  23. package/cmds/visualizations.js +116 -0
  24. package/docker-compose.yml +11 -0
  25. package/migrations/20230222011333-create-health-table.sql +10 -0
  26. package/migrations/20230222011334-outcomes.sql +42 -0
  27. package/migrations/20231206093700-seed-outcomes-table.sql +9 -0
  28. package/migrations.js +20 -0
  29. package/package.json +50 -0
  30. package/scripts/generateBacklogByTeam.sh +20 -0
  31. package/scripts/generateBacklogHealth.sh +12 -0
  32. package/scripts/generateChart.sh +7 -0
  33. package/scripts/generateFeatureTeamBacklog.sh +16 -0
  34. package/scripts/generateSprintOutcomePlot.sh +12 -0
  35. package/scripts/sprintOutcomes.sh +29 -0
  36. package/sql/bugsOpenedDuringSprint.sql +14 -0
  37. package/sql/bugsOpenedDuringSprintFeature.sql +14 -0
  38. package/sql/capacityAdjustedVelocity.sql +48 -0
  39. package/sql/carryOver.sql +33 -0
  40. package/sql/commitment.sql +30 -0
  41. package/sql/commitmentMet.sql +60 -0
  42. package/sql/completed.sql +40 -0
  43. package/sql/descoped.sql +21 -0
  44. package/sql/pullForward.sql +19 -0
  45. package/sql/pullForwardFeature.sql +10 -0
  46. package/sql/sprintOutcomes.sql +25 -0
  47. package/sql/sprint_outcomes.sql +23 -0
  48. package/src/addOutcomes.js +94 -0
  49. package/src/addSprint.js +108 -0
  50. package/src/getSprintDetails.js +88 -0
  51. package/utils/display.js +68 -0
  52. package/utils/input.js +24 -0
  53. package/utils/readConfig.js +74 -0
@@ -0,0 +1,103 @@
1
+ library(tidyverse)
2
+ library(ggplot2)
3
+
4
+ # sqlite3 --header --csv tracker.db < sql/sprintOutcomes.sql > /tmp/outcomes.csv
5
+
6
+ outcomes = read_csv('/tmp/outcomes.csv')
7
+
8
+ outcomes <- outcomes %>%
9
+ tibble() %>%
10
+ filter(story_points != 0) %>%
11
+ mutate(
12
+ end_date = as.Date(end_date)
13
+ )
14
+
15
+ # compute velocity statistics
16
+
17
+ entire_project_velocity = (outcomes %>%
18
+ filter(outcome == 'B-Delivered' ) %>%
19
+ summarize( average_velocity = mean(story_points)))$average_velocity
20
+
21
+ average_velocity = (outcomes %>%
22
+ filter(outcome == 'B-Delivered') %>%
23
+ filter(!sprint_name %in% c('Accessibility Improvement 0','Accessibility Improvements 1')) %>%
24
+ summarize( average_velocity = mean(story_points)))$average_velocity
25
+
26
+ # Compute 95% band velocity
27
+
28
+ outcomeColors = c("A-Commitment" = "#0B4F6C",
29
+ "Carryover" = "#D34E24",
30
+ "Carryover - External" = "#d3248d",
31
+ "Descoped" ="#A39BA8",
32
+ "Unplanned - Bug" ="#F28123",
33
+ "Unplanned - Story" = "#F7F052",
34
+ "B-Delivered" ="#00A878")
35
+ outcomeFills = c("A-Commitment" = "#0B4F6C",
36
+ "Carryover" = "#D34E24",
37
+ "Carryover - External" = "#d3248d",
38
+ "Descoped" ="#A39BA8",
39
+ "Unplanned - Bug" ="#F28123",
40
+ "Unplanned - Story" = "#F7F052",
41
+ "B-Delivered" ="#00A878")
42
+
43
+ chartTitle = str_c("Sprint Outcomes - ",unique(outcomes$project_name))
44
+
45
+ plot<-ggplot(outcomes,
46
+ aes(fill=outcome,
47
+ y=story_points,
48
+ # x=end_date
49
+ #x = str_wrap(sprint_name,15)
50
+ x = reorder(str_wrap(sprint_name,15),end_date)
51
+ )
52
+ )+
53
+ geom_bar(position="dodge", stat="identity",color = "black") +
54
+ # add bar labels
55
+ geom_text(data = outcomes %>% mutate(story_points = na_if(story_points,0)),
56
+ aes(label=story_points, y = story_points + .5),
57
+ position = position_dodge(0.9)) +
58
+ # trendline
59
+ geom_smooth(aes(#x=factor(end_date),
60
+ x=factor(reorder(str_wrap(sprint_name,15),end_date)),
61
+ y=story_points,group=outcome),
62
+ data = outcomes%>%filter(outcome=="B-Delivered"),
63
+ #method = "lm", # no method = curved line
64
+ se=FALSE,
65
+ show.legend = FALSE,
66
+ linetype = "dashed",
67
+ color = "#00A878") +
68
+ # TODO better position this annotation
69
+ annotate(geom="text",
70
+ x=length(unique(outcomes$sprint_name))+.25,
71
+ y=average_velocity + 1.5,
72
+ label="Trend Line") +
73
+ # average velocity line
74
+ geom_hline(yintercept=average_velocity,linetype = "dotted", color = "#00A878") +
75
+ annotate(geom="text", x=.75, y=average_velocity + 1.5, label="Average\nVelocity") +
76
+ # plot labels
77
+ labs(title=chartTitle,
78
+ # subtitle = 'Excluding the Data Science, Product Ownership and QA Teams',
79
+ x ="",
80
+ y = "Story Points",
81
+ fill='Outcomes') +
82
+ #colors
83
+ scale_colour_manual(values = outcomeColors) +
84
+ scale_fill_manual(values = outcomeFills) +
85
+ #themes
86
+ theme(
87
+ plot.title = element_text(family = "roboto"),
88
+ axis.title.x = element_text(family = "roboto"),
89
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
90
+ axis.title.y = element_text(family = "roboto"),
91
+ panel.grid.major.y = element_line(colour = "#D9D9D9"), panel.grid.minor.y = element_line(colour = "#D9D9D9"),
92
+ panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(),
93
+ # background color
94
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
95
+ legend.position = "bottom",
96
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
97
+ )
98
+
99
+ # plot # commented out for docker
100
+
101
+ ggsave(str_c("~/projects/sprint-tracker/",str_replace(unique(outcomes$project_name)," ",""),"SprintOutcomesCli.png"),plot=plot,bg="white", width = 2882, height = 1700, units = "px")
102
+
103
+ print(str_c("Saving to ",str_c("~/projects/sprint-tracker/",str_replace(unique(outcomes$project_name)," ",""),"SprintOutcomesCli.png"),sep = " "))
@@ -0,0 +1,143 @@
1
+ library(tidyverse)
2
+ library(ggplot2)
3
+
4
+ # sqlite3 --header --csv tracker.db < sprint_outcomes.sql > /tmp/outcomes.csv
5
+
6
+ library(DBI)
7
+ library(RSQLite)
8
+ con <- dbConnect(RSQLite::SQLite(),"~/projects/sprint-tracker/tracker.db")
9
+
10
+ res <- dbSendQuery(con, "-- SQLite
11
+ SELECT
12
+ p.name project_name,
13
+ s.id sprint_id,
14
+ s.sprint_name,
15
+ s.end_date,
16
+ case
17
+ when o.name = 'Commitment' then 'A-Commitment'
18
+ when o.name = 'Delivered' then 'B-Delivered'
19
+ else o.name
20
+ end outcome,
21
+ -- s.commited_points,
22
+ -- so.issue_count,
23
+ -- so.story_points,
24
+ ifnull(so.story_points, so.issue_count) AS story_points -- accounts for unpointed bug stories
25
+
26
+ from projects p
27
+
28
+ JOIN sprints s on s.project_id = p.id
29
+ JOIN sprint_outcomes so on so.sprint_id = s.id
30
+ JOIN outcomes o on o.id = so.outcome_id
31
+
32
+ WHERE
33
+ -- p.id = 10 -- Helo Project Team
34
+ p.id = 11 -- Feature Team One
35
+
36
+ ")
37
+
38
+
39
+ # TODO query sqlite directly
40
+ outcomes <- dbFetch(res)
41
+
42
+ # Clear the result
43
+ dbClearResult(res)
44
+
45
+ # Disconnect from the database
46
+ dbDisconnect(con)
47
+
48
+ outcomes <- outcomes %>%
49
+ tibble() %>%
50
+ filter(story_points != 0) %>%
51
+ mutate(
52
+ end_date = as.Date(end_date)
53
+ )
54
+
55
+ # compute velocity statistics
56
+
57
+ entire_project_velocity = (outcomes %>%
58
+ filter(outcome == 'B-Delivered' ) %>%
59
+ summarize( average_velocity = mean(story_points)))$average_velocity
60
+
61
+ average_velocity = (outcomes %>%
62
+ filter(outcome == 'B-Delivered') %>%
63
+ filter(!sprint_name %in% c('Accessibility Improvement 0','Accessibility Improvements 1')) %>%
64
+ summarize( average_velocity = mean(story_points)))$average_velocity
65
+
66
+ # Compute 95% band velocity
67
+
68
+ outcomeColors = c("A-Commitment" = "#0B4F6C",
69
+ "Carryover" = "#D34E24",
70
+ "Carryover - External" = "#d3248d",
71
+ "Descoped" ="#A39BA8",
72
+ "Unplanned - Bug" ="#F28123",
73
+ "Unplanned - Story" = "#F7F052",
74
+ "B-Delivered" ="#00A878")
75
+ outcomeFills = c("A-Commitment" = "#0B4F6C",
76
+ "Carryover" = "#D34E24",
77
+ "Carryover - External" = "#d3248d",
78
+ "Descoped" ="#A39BA8",
79
+ "Unplanned - Bug" ="#F28123",
80
+ "Unplanned - Story" = "#F7F052",
81
+ "B-Delivered" ="#00A878")
82
+
83
+ chartTitle = str_c("Sprint Outcomes - ",unique(outcomes$project_name))
84
+
85
+ plot<-ggplot(outcomes,
86
+ aes(fill=outcome,
87
+ y=story_points,
88
+ # x=end_date
89
+ #x = str_wrap(sprint_name,15)
90
+ x = reorder(str_wrap(sprint_name,15),end_date)
91
+ )
92
+ )+
93
+ geom_bar(position="dodge", stat="identity",color = "black") +
94
+ # add bar labels
95
+ geom_text(data = outcomes %>% mutate(story_points = na_if(story_points,0)),
96
+ aes(label=story_points, y = story_points + .5),
97
+ position = position_dodge(0.9)) +
98
+ # trendline
99
+ geom_smooth(aes(#x=factor(end_date),
100
+ x=factor(reorder(str_wrap(sprint_name,15),end_date)),
101
+ y=story_points,group=outcome),
102
+ data = outcomes%>%filter(outcome=="B-Delivered"),
103
+ #method = "lm", # no method = curved line
104
+ se=FALSE,
105
+ show.legend = FALSE,
106
+ linetype = "dashed",
107
+ color = "#00A878") +
108
+ # TODO better position this annotation
109
+ annotate(geom="text",
110
+ x=length(unique(outcomes$sprint_name))+.25,
111
+ y=average_velocity + 1.5,
112
+ label="Trend Line") +
113
+ # average velocity line
114
+ geom_hline(yintercept=average_velocity,linetype = "dotted", color = "#00A878") +
115
+ annotate(geom="text", x=.75, y=average_velocity + 1.5, label="Average\nVelocity") +
116
+ # plot labels
117
+ labs(title=chartTitle,
118
+ # subtitle = 'Excluding the Data Science, Product Ownership and QA Teams',
119
+ x ="",
120
+ y = "Story Points",
121
+ fill='Outcomes') +
122
+ #colors
123
+ scale_colour_manual(values = outcomeColors) +
124
+ scale_fill_manual(values = outcomeFills) +
125
+ #themes
126
+ theme(
127
+ plot.title = element_text(family = "roboto"),
128
+ axis.title.x = element_text(family = "roboto"),
129
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
130
+ axis.title.y = element_text(family = "roboto"),
131
+ panel.grid.major.y = element_line(colour = "#D9D9D9"), panel.grid.minor.y = element_line(colour = "#D9D9D9"),
132
+ panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(),
133
+ # background color
134
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
135
+ legend.position = "bottom",
136
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
137
+ )
138
+
139
+ # plot # commented out for docker
140
+
141
+ ggsave(str_c("~/projects/sprint-tracker/",str_replace(unique(outcomes$project_name)," ",""),"SprintOutcomes.png"),plot=plot,bg="white", width = 2882, height = 1700, units = "px")
142
+
143
+ print(str_c("Saving to ",str_c("~/projects/sprint-tracker/",str_replace(unique(outcomes$project_name)," ",""),"SprintOutcomes.png"),sep = " "))
@@ -0,0 +1,157 @@
1
+ library(tidyverse)
2
+ library(lubridate)
3
+ library(ggplot2)
4
+
5
+
6
+ # json2csv -i Sprint12Transitions.json --unwind changelog --flatten-objects -o Sprint12Transitions.csv
7
+
8
+ # transitions = read_csv('~/projects/sprint-tracker/Sprint12Transitions.csv') %>%
9
+ transitions = read_csv('~/projects/sprint-tracker/code-review/transitions.csv') %>%
10
+ rename(timestamp = changelog.timestamp,
11
+ from = changelog.transition.from,
12
+ to = changelog.transition.to) %>%
13
+ mutate(transition = str_c(from,to,sep="-")) %>%
14
+ filter(str_detect(key,"ACDC"))
15
+
16
+
17
+ # single = transitions %>%
18
+ # filter(key == 'ACDC-250') %>%
19
+ # filter(transition %in% c("In Progress-Code Review","Code Review-QA Review")) %>%
20
+ # pivot_wider(id_cols = c(key,summary),
21
+ # names_from = transition,
22
+ # values_from = timestamp) %>%
23
+ # mutate(
24
+ # duration = `Code Review-QA Review` - `In Progress-Code Review`
25
+ # )
26
+
27
+ time_in_code_review = transitions %>%
28
+ filter(transition %in% c("In Progress-Code Review","Code Review-QA Review")) %>%
29
+ pivot_wider(id_cols = c(sprint,key,summary),
30
+ names_from = transition,
31
+ values_from = timestamp,
32
+ # the min function is to get the earliest transition
33
+ values_fn = min) %>%
34
+ relocate(`In Progress-Code Review`,.after=summary) %>%
35
+ mutate(
36
+ duration = difftime(`Code Review-QA Review`,`In Progress-Code Review`,units = "days")
37
+ )
38
+
39
+ summary = time_in_code_review %>%
40
+ group_by(sprint) %>%
41
+ summarise(mean = mean(duration,na.rm=TRUE),
42
+ min = min(duration,na.rm=TRUE),
43
+ max=max(duration,na.rm=TRUE),
44
+ sd=sd(duration,na.rm=TRUE),
45
+ count = n()
46
+ )
47
+ print(summary)
48
+
49
+ summary %>%
50
+ filter(sprint != 'Bulk Assign API') %>%
51
+ pivot_longer(
52
+ # cols = c(mean_time,max_time,min_time),
53
+ cols = c(mean,max,min),
54
+ names_to = "statistic",
55
+ values_to = "days",
56
+ values_transform = as.numeric
57
+ ) %>%
58
+ select(sprint,statistic,days) %>%
59
+ # filter(statistic=="mean") %>%
60
+ ggplot(
61
+ aes(x=reorder(sprint,as.numeric(str_extract(sprint,"[0-9]+"))),
62
+ y=days,
63
+ color=statistic,
64
+ group = statistic)
65
+ ) +
66
+ geom_point() +
67
+ geom_line() +
68
+ labs(title="Time in Code Review", x ="", y = "Days") +
69
+ theme(
70
+ plot.title = element_text(family = "roboto"),
71
+ axis.title.x = element_text(family = "roboto"),
72
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
73
+ axis.title.y = element_text(family = "roboto"),
74
+ panel.grid.major.y = element_line(colour = "#D9D9D9"), panel.grid.minor.y = element_line(colour = "#D9D9D9"),
75
+ panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(),
76
+ # background color
77
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
78
+ legend.position = "bottom",
79
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
80
+ )
81
+
82
+
83
+ mean_code_review = mean(time_in_code_review$duration,na.rm=TRUE)
84
+ meet_SLA = time_in_code_review %>% filter(duration <= 1 ) %>% count() %>% pull()
85
+ percent_meet_SLA = (meet_SLA/count(time_in_code_review)) %>% pull()
86
+
87
+ time_in_code_review %>%
88
+ filter(sprint != 'Bulk Assign API') %>%
89
+ ggplot(
90
+ aes(x=reorder(sprint,as.numeric(str_extract(sprint,"[0-9]+"))),
91
+ y=duration)
92
+ ) +
93
+ geom_point()+
94
+ geom_jitter()+
95
+ geom_hline(yintercept = 1, color = "red") +
96
+ labs(title="Time in Code Review",
97
+ subtitle = "1 day SLA",
98
+ x ="",
99
+ y = "Days") +
100
+ annotate(geom="text",
101
+ x=1,
102
+ y=9,
103
+ hjust = 0,
104
+ color = "red",
105
+ label=str_c(scales::label_percent()(percent_meet_SLA)," meet 1 day SLA for review")) +
106
+ theme(
107
+ plot.title = element_text(family = "roboto"),
108
+ axis.title.x = element_text(family = "roboto"),
109
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
110
+ axis.title.y = element_text(family = "roboto"),
111
+ panel.grid.major.y = element_line(colour = "#D9D9D9"), panel.grid.minor.y = element_line(colour = "#D9D9D9"),
112
+ panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(),
113
+ # background color
114
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
115
+ legend.position = "bottom",
116
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
117
+ )
118
+
119
+ ggsave("~/projects/sprint-tracker/time_in_code_review.png",bg="white", width = 2882, height = 1700, units = "px")
120
+
121
+
122
+ time_in_code_review %>%
123
+ filter(sprint != 'Bulk Assign API') %>%
124
+ ggplot(aes(x=duration)) +
125
+ geom_histogram(binwidth=1,color="black", fill="white") +
126
+ stat_bin(binwidth=1,
127
+ geom="text",
128
+ colour="black",
129
+ size=3.5,
130
+ aes(label=..count..),
131
+ position=position_stack(vjust=0.5)) +
132
+ # geom_density(alpha=.2, fill="#FF6666") +
133
+ scale_x_continuous(minor_breaks = seq(1, 20, 1),
134
+ # limits=c(0,15)
135
+ ) +
136
+ labs(title="Time in Code Review",
137
+ subtitle = "Histogram of 1 day buckets",
138
+ x ="",
139
+ y = "Days") +
140
+ theme(
141
+ plot.title = element_text(family = "roboto"),
142
+ axis.title.x = element_text(family = "roboto"),
143
+ # axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
144
+ axis.title.y = element_text(family = "roboto"),
145
+ panel.grid.major.y = element_line(colour = "#D9D9D9"),
146
+ panel.grid.minor.y = element_line(colour = "#D9D9D9"),
147
+ panel.grid.major.x = element_line(colour = "#D9D9D9"),
148
+ panel.grid.minor.x = element_line(colour = "#D9D9D9"),
149
+ # background color
150
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
151
+ legend.position = "bottom",
152
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
153
+ )
154
+
155
+ ggsave("~/projects/sprint-tracker/time_in_code_review_histogram.png",bg="white", width = 2882, height = 1700, units = "px")
156
+
157
+
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ Sprint Tracker
2
+ ===============
3
+
4
+ Collects sprint outcome information and generates a visualization of sprint outcomes.
5
+
6
+ ## Visualizations
7
+
8
+ Using the `visualization` command, you can generate a sprint outcome and velocity visualization. An example is shown below, with sprint names across the bottom. For each sprint, it compares the commitment and delivered story points, as well as some information about stories added and removed from the sprint.
9
+
10
+
11
+ ![](./DFSprintOutcomesCli.png)
12
+
13
+ ## Command Information
14
+
15
+ ```
16
+ sprint-tracker <cmd> [args]
17
+
18
+ Commands:
19
+ sprint-tracker addOutcome Menu driven process to add outcomes to a
20
+ sprint
21
+ sprint-tracker addProject Prompts to add a new project to the database
22
+ sprint-tracker addSprint Prompts to add a new sprint to an existing
23
+ project
24
+ sprint-tracker config Creates a config file, prompting for required
25
+ inputs
26
+ sprint-tracker getSprint Gets sprint details
27
+ sprint-tracker visualizations Creates Feature Team visualizations with R.
28
+ Must have docker, rstudio-tidyverse container
29
+ and more installed
30
+ sprint-tracker create-database Creates and initializes the empty database
31
+ specified in the config
32
+ sprint-tracker upgrade-database Upgrades database with new structure
33
+ sprint-tracker completion Outputs bash/zsh-completion shortcuts for
34
+ commands and options to add to .bashrc or
35
+ .bash_profile
36
+
37
+ Options:
38
+ --version Show version number [boolean]
39
+ --help Show help [boolean]
40
+ ```
41
+ ## Dependencies
42
+
43
+ This CLI application relies on a local [sqlite](https://www.sqlite.org/download.html) database, [docker](https://docs.docker.com/desktop/?_gl=1*ogftl2*_ga*OTE5MjI3MDc1LjE3MDE5MDA5NTE.*_ga_XJWPQMJYHQ*MTcwMTkwMDk1MS4xLjEuMTcwMTkwMDk5My4xOC4wLjA.), and the [rocker](https://hub.docker.com/r/rocker/tidyverse)/tidyverse](https://hub.docker.com/r/rocker/tidyverse) image to function. Installation of these dependencies is outside of the scope of this project as the steps vary by OS.
44
+
45
+ On Mac, these can be installed with homebrew.
46
+
47
+ ```bash
48
+ brew install docker sqlite
49
+ docker pull rocker/tidyverse
50
+ ```
51
+
52
+ ## General Usage
53
+
54
+ 1. Update the config file with the path to the database `sprint-tracker config`. The database doesn't need to exist yet. This step only needs to be done the first time.
55
+ 7. Create the database and run the migrations `sprint-tracker create-database`. This step only needs to be done the first time.
56
+ 8. Run `sprint-tracker addProject` to add a project to the database. This step can be repeated as new projects are started.
57
+ 9. Add the first sprint with `sprint-tracker addSprint` optionally, adding the sprint commitment. This step can be repeated for subsequent sprints.
58
+ 10. At the end of the sprint, record sprint outcomes `sprint-tracker addOutcome`. This will allow you to log information about stories and points completed, committed, pulled forward, carried over, and bugs.
59
+ 11. Generate the cumulative sprint outcomes visualization with `sprint-tracker visualizations`. If you do not have the [rocker/tidyverse](https://hub.docker.com/r/rocker/tidyverse) image, it will automatically pull it, which will take a little bit of time
60
+
61
+
62
+ ## Configuration
63
+
64
+ A configuration file `~/sprintTracker.json` is required with the full path to your tracker database
65
+
66
+ ```json
67
+ {
68
+ "database":"/Users/beauraines/projects/sprint-tracker/tracker.db"
69
+ }
70
+ ```
71
+
72
+ ## Installation and Setup for Development
73
+
74
+ 1. Clone the repository
75
+ 2. Ensure you have sqlite3 and docker installed. See [Dependencies](#dependencies).
76
+ 3. Install npm dependencies `npm i`
77
+ 4. Globally install the sprint tracker `npm -g i`
78
+ 5. Follow the [General Usage](#general-usage) instructions.
79
+
80
+ ## Tracking Outcomes
81
+
82
+ ### Project
83
+
84
+ This table will store project master data.
85
+
86
+
87
+ ### Sprint
88
+ Sprint details including the dates and original commitment. The original commitment will not change and can be sourced from the Jira "Velocity Report" for the sprint.
89
+
90
+ Non-working days and PTO will be recorded to be able to normalize Sprints with different durations.
91
+
92
+
93
+ ### Sprint Outcomes
94
+
95
+ 1. Commitment : Initial sprint commitment
96
+ 1. Delivered : Work completed this sprint
97
+ 1. Unplanned - Story : All unplanned work brought into the sprint, whether completed or not
98
+ 1. Unplanned - Bug : Unplanned work without a pre-existing story that was deemed critical enough to add to the sprint
99
+ 1. Descoped : Committed work, removed from the sprint
100
+ 1. Carryover : Incomplete, started or unstarted, work from this sprint, assumed that it will carryover to the next sprint
101
+ 1. Carryover - External : Work that was carried over due to blockers external to the scrum team. Use of this is optional, as an agile team should be fully self-sufficient.
102
+
103
+
104
+ ### ERD
105
+
106
+ ```mermaid
107
+ erDiagram
108
+ PROJECTS {
109
+ int id
110
+ string name
111
+ string pm_name
112
+ string pm_email
113
+ string client
114
+ string project_number
115
+ date start_date
116
+ date end_date
117
+
118
+ }
119
+ SPRINTS {
120
+ int id
121
+ int project_id
122
+ string sprint_name
123
+ date start_date
124
+ date end_date
125
+ number committed_points
126
+ number committed_stories
127
+ number non_working_days
128
+ number developers
129
+ number developer_pto_days
130
+ }
131
+
132
+ SPRINT_OUTCOMES {
133
+ int id
134
+ int sprint_id
135
+ int outcome_id
136
+ int issue_count
137
+ int story_points
138
+ text notes
139
+ }
140
+
141
+ OUTCOMES {
142
+ int id
143
+ string name
144
+ text description
145
+ }
146
+
147
+ PROJECTS ||--o{ SPRINTS : ""
148
+ SPRINTS ||--|{ SPRINT_OUTCOMES : ""
149
+ OUTCOMES ||--o{ SPRINT_OUTCOMES: ""
150
+ ```
151
+
152
+
153
+
154
+ ## Generating Visualizations with R and Docker
155
+
156
+ There are several R scripts included in the project repository, more than just the sprint outcomes. They can be run from the command line using docker and the `rocker/tidyverse` image as shown below.
157
+
158
+ ```bash
159
+ # PROJECT_DIR must have trailing slash
160
+ PROJECT_DIR=/Users/beauraines/projects/
161
+ IMAGE=rocker/tidyverse
162
+ docker run -it --rm -v ${PROJECT_DIR}:/home/rstudio/projects/ ${IMAGE} su -c 'Rscript /home/rstudio/projects/sprint-tracker/R/sprintOutcomes.R' rstudio
163
+ ```
164
+
165
+ ## Roadmap
166
+
167
+ 1. ~~Build local database using sqlite3~~
168
+ 2. ~~Script visualizations, possibly with R, graph-cli, chartsjs, PowerBI or AWS QuickSight~~
169
+ 3. [Backlog health tracking](https://github.com/beauraines/sprint-tracker/issues/36)
170
+ 4. Write API for adding data
171
+ 5. Write API for getting visualizations
172
+ 6. ~~Create database migrations~~
173
+ 7. Deploy to cloud
174
+ 8. Add sprint metrics
175
+ 1. Average velocity over life of project
176
+ 2. Recent average velocity
177
+ 3. 90% confidence range. See <https://resources.scrumalliance.org/Article/its-target-forecast>
178
+ 4. Daily points per developer
179
+
180
+
181
+
package/cli.js ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('yargs')
4
+ .scriptName("sprint-tracker")
5
+ .usage('$0 <cmd> [args]')
6
+ .commandDir('cmds')
7
+ .command({
8
+ command: 'create-database',
9
+ desc: 'Creates and initializes the empty database specified in the config',
10
+ handler: async () => {
11
+ var sqlite3 = require('sqlite3').verbose();
12
+ let {CommandsRunner, SQLite3Driver} = require('node-db-migration');
13
+ const {homedir} = require('os');
14
+ const {readConfig} = require('./utils/readConfig.js')
15
+ const path = require('path');
16
+
17
+ const filename = 'sprintTracker.json'
18
+ const configFile = path.join(homedir(),filename)
19
+ const config = await readConfig(configFile)
20
+ const database = config.database
21
+ var db = new sqlite3.Database(database);
22
+ let migrations = new CommandsRunner({
23
+ driver: new SQLite3Driver(db),
24
+ directoryWithScripts: __dirname + '/migrations',
25
+ });
26
+ await migrations.run('init')
27
+ await migrations.run('migrate')
28
+ }
29
+ })
30
+ .command({
31
+ command: 'upgrade-database',
32
+ desc: 'Upgrades database with new structure',
33
+ handler: async () => {
34
+ var sqlite3 = require('sqlite3').verbose();
35
+ let {CommandsRunner, SQLite3Driver} = require('node-db-migration');
36
+ const {homedir} = require('os');
37
+ const {readConfig} = require('./utils/readConfig.js')
38
+ const path = require('path');
39
+
40
+ const filename = 'sprintTracker.json'
41
+ const configFile = path.join(homedir(),filename)
42
+ const config = await readConfig(configFile)
43
+ const database = config.database
44
+ var db = new sqlite3.Database(database);
45
+ let migrations = new CommandsRunner({
46
+ driver: new SQLite3Driver(db),
47
+ directoryWithScripts: __dirname + '/migrations',
48
+ });
49
+ await migrations.run('migrate')
50
+ }
51
+ })
52
+ .completion('completion', 'Outputs bash/zsh-completion shortcuts for commands and options to add to .bashrc or .bash_profile')
53
+ .demandCommand()
54
+ .help()
55
+ .argv