@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,144 @@
1
+ library(tidyverse)
2
+ library(lubridate)
3
+
4
+ burndown = tibble(
5
+ date = seq.Date(as.Date('2022-11-16'),as.Date('2022-11-28'), by=1),
6
+ points = c(10,10,9,9,7,7,2,2,2,2,2,0,0),
7
+ done = c(10,10,10,9,9,9,7,7,7,2,2,2,2)
8
+ )
9
+
10
+ burndown %>%
11
+ filter(! date %in% c('2022-11-24','2022-11-25')) %>%
12
+ ggplot(aes(x=date)) +
13
+ geom_line(mapping=aes(y=points)) +
14
+ geom_line(aes(y=done),color="blue")
15
+
16
+
17
+
18
+ foo = tibble_row(
19
+ issue = "ABC-123",
20
+ qa_review = '2022-07-15T14:27:11.011-0700',
21
+ done = "2022-07-18T12:19:02.457-0700"
22
+ )
23
+
24
+ foo = foo %>% tibble_row(
25
+ issue = "ABC-124",
26
+ qa_review = '2022-07-18T15:39:09.969-0700',
27
+ done = "2022-07-19T06:04:54.823-0700"
28
+ )
29
+
30
+ sprint_data = tribble(
31
+ ~issue, ~qa_review, ~done,~points,
32
+ "ABC-123",'2022-07-15T14:27:11.011-0700',"2022-07-18T12:19:02.457-0700",3,
33
+ "ABC-124",'2022-07-18T15:39:09.969-0700',"2022-07-19T06:04:54.823-0700",2
34
+ ) %>% mutate(
35
+ qa_review=as_datetime(qa_review),
36
+ done = as_datetime(done)
37
+ )
38
+
39
+
40
+ sprint_raw = read_csv('~/projects/sprint-tracker/burndown.csv') %>%
41
+ rename(
42
+ issue = key,
43
+ from = changelog.transition.from,
44
+ to = changelog.transition.to,
45
+ timestamp = changelog.timestamp
46
+ ) %>%
47
+ pivot_longer(cols = c(from,to)) %>%
48
+ filter(name == "to", value %in% c("Done","Code Review","QA Review") )%>%
49
+ pivot_wider(names_from = "value",
50
+ id_cols = -name,
51
+ values_from = timestamp)
52
+
53
+
54
+
55
+ sprint_start = as_datetime('2022-11-09T16:20:00-0700')
56
+ sprint_end = as_datetime('2022-11-23T16:00:00-0700')
57
+ sprint_points = sum(sprint_raw$points,na.rm = TRUE)
58
+
59
+ # sprint_raw %>%
60
+ # select(qa_review,points) %>%
61
+ # mutate(points = points * -1) %>%
62
+ # rename(qa_points = points,
63
+ # date = qa_review)
64
+
65
+
66
+ pointChanges = tribble(
67
+ ~date,~qa_points,~done_points,~code_review_points,
68
+ sprint_start,sprint_points,sprint_points,sprint_points
69
+ ) %>% bind_rows(
70
+ sprint_raw %>%
71
+ select(`QA Review` ,points) %>%
72
+ filter(!is.na(`QA Review`)) %>%
73
+ mutate(points = points * -1) %>%
74
+ rename(qa_points = points,
75
+ date = `QA Review`)
76
+ ) %>% bind_rows(
77
+ sprint_raw %>%
78
+ select(Done,points) %>%
79
+ filter(!is.na(Done)) %>%
80
+ mutate(points = points * -1) %>%
81
+ rename(done_points = points,
82
+ date = Done)
83
+ ) %>% bind_rows(
84
+ sprint_raw %>%
85
+ select(`Code Review`,points) %>%
86
+ filter(!is.na(`Code Review`)) %>%
87
+ mutate(points = points * -1) %>%
88
+ rename(code_review_points = points,
89
+ date = `Code Review`) %>%
90
+ rowwise() %>%
91
+ mutate(date = if (as_datetime(date) < as_datetime(sprint_start)) sprint_start + 1 else date)
92
+ ) %>%
93
+ arrange(date) %>%
94
+ mutate_at(~replace(., is.na(.), 0),.vars = vars(ends_with("points"))) ## Replace all NA with zero
95
+
96
+ ## Add the running total
97
+ library(runner)
98
+ pointChanges <- pointChanges %>%
99
+ mutate(
100
+ qa_burndown = runner(qa_points,f=sum),
101
+ done_burndown = runner(done_points,f=sum),
102
+ code_review_burndown = runner(code_review_points,f=sum)
103
+ )
104
+
105
+
106
+ ## Plot it
107
+
108
+ pointChanges %>%
109
+ ggplot(aes(x=date)) +
110
+ geom_line(mapping=aes(y=qa_burndown)) +
111
+ geom_point(mapping=aes(y=qa_burndown)) +
112
+ geom_line(aes(y=done_burndown),color="blue")+
113
+ geom_point(aes(y=done_burndown),color="blue") +
114
+ geom_line(mapping=aes(y=code_review_burndown),color="red") +
115
+ geom_point(mapping=aes(y=code_review_burndown),color="red") +
116
+ labs(title="Sprint Burndown",
117
+ subtitle = "Not including additions to the in-progress sprint",
118
+ x ="",
119
+ y = "Story Points") +
120
+ ylim(0,NA) +
121
+ scale_x_datetime(date_labels = "%b-%d",
122
+ date_breaks = "1 day",
123
+ limits=c(sprint_start,sprint_end)) +
124
+ theme(
125
+ plot.title = element_text(family = "roboto"),
126
+ axis.title.x = element_text(family = "roboto"),
127
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
128
+ axis.title.y = element_text(family = "roboto"),
129
+ panel.grid.major.y = element_line(colour = "#D9D9D9"),
130
+ panel.grid.minor.y = element_line(colour = "#D9D9D9"),
131
+ panel.grid.major.x = element_line(colour = "#D9D9D9"),
132
+ panel.grid.minor.x = element_line(colour = "#D9D9D9"),
133
+ # background color
134
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
135
+ legend.position = "right",
136
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
137
+ )
138
+ # Add Theme
139
+ # Add Titles
140
+ # Add Legend
141
+
142
+ ggsave("~/projects/sprint-tracker/burndown.png",bg="white", width = 2100, height = 700, units="px")
143
+
144
+
@@ -0,0 +1,154 @@
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(),"/home/rstudio/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
+ o.name outcome,
17
+ -- s.commited_points,
18
+ -- so.issue_count,
19
+ -- so.story_points,
20
+ ifnull(so.story_points, so.issue_count) AS story_points -- accounts for unpointed bug stories
21
+
22
+ from projects p
23
+
24
+ JOIN sprints s on s.project_id = p.id
25
+ JOIN sprint_outcomes so on so.sprint_id = s.id
26
+ JOIN outcomes o on o.id = so.outcome_id and o.id in (1,6,7) -- only commitment, deliver and external carry over
27
+
28
+ WHERE
29
+ p.pm_name like 'Beau%'
30
+ and p.id = 4 -- ECOM Enhancements
31
+ -- p.name = 'FHIR'
32
+ -- p.name = 'DF'
33
+ and s.end_date <= date('now')
34
+ order by sprint_id,s.end_date,outcome
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 Adjusted Devlivery
56
+
57
+ outcomes <- outcomes %>% pivot_wider(names_from = outcome,
58
+ values_from = story_points) %>%
59
+ rowwise() %>%
60
+ mutate(
61
+ `Delivered (adjusted)` = sum(c(`Carryover - External`,Delivered),na.rm =TRUE)
62
+ ) %>%
63
+ ungroup() %>%
64
+ select(-Delivered,-`Carryover - External`) %>%
65
+ rename('B-Delivered' = `Delivered (adjusted)`,
66
+ 'A-Commitment' = Commitment) %>%
67
+ pivot_longer(cols = c("B-Delivered","A-Commitment"),
68
+ values_to = 'story_points',
69
+ names_to = "outcome")
70
+
71
+ # compute velocity statistics
72
+
73
+ entire_project_velocity = (outcomes %>%
74
+ filter(outcome == 'B-Delivered' ) %>%
75
+ summarize( average_velocity = mean(story_points)))$average_velocity
76
+
77
+ average_velocity = (outcomes %>%
78
+ filter(outcome == 'B-Delivered') %>%
79
+ filter(!sprint_name %in% c('Accessibility Improvement 0','Accessibility Improvements 1')) %>%
80
+ summarize( average_velocity = mean(story_points)))$average_velocity
81
+
82
+ # Compute 95% band velocity
83
+
84
+ outcomeColors = c("A-Commitment" = "#0B4F6C",
85
+ # "Carryover" = "#D34E24",
86
+ # "Carryover - External" = "#d3248d",
87
+ # "Descoped" ="#A39BA8",
88
+ # "Unplanned - Bug" ="#F28123",
89
+ # "Unplanned - Story" = "#F7F052",
90
+ "B-Delivered" ="#00A878")
91
+ outcomeFills = c("A-Commitment" = "#0B4F6C",
92
+ # "Carryover" = "#D34E24",
93
+ # "Carryover - External" = "#d3248d",
94
+ # "Descoped" ="#A39BA8",
95
+ # "Unplanned - Bug" ="#F28123",
96
+ # "Unplanned - Story" = "#F7F052",
97
+ "B-Delivered" ="#00A878")
98
+
99
+ chartTitle = str_c("Sprint Outcomes - ",unique(outcomes$project_name))
100
+
101
+ plot<-ggplot(outcomes,
102
+ aes(fill=outcome,
103
+ y=story_points,
104
+ # x=end_date
105
+ #x = str_wrap(sprint_name,15)
106
+ x = reorder(str_wrap(sprint_name,15),end_date)
107
+ )
108
+ )+
109
+ geom_bar(position="dodge", stat="identity",color = "black") +
110
+ # add bar labels
111
+ geom_text(data = outcomes %>% mutate(story_points = na_if(story_points,0)),
112
+ aes(label=story_points, y = story_points + .5),
113
+ position = position_dodge(0.9)) +
114
+ # trendline
115
+ geom_smooth(aes(#x=factor(end_date),
116
+ x=factor(reorder(str_wrap(sprint_name,15),end_date)),
117
+ y=story_points,group=outcome),
118
+ data = outcomes%>%filter(outcome=="B-Delivered"),
119
+ #method = "lm", # no method = curved line
120
+ se=FALSE,
121
+ show.legend = FALSE,
122
+ linetype = "dashed",
123
+ color = "#00A878") +
124
+ # TODO better position this annotation
125
+ annotate(geom="text",
126
+ x=length(unique(outcomes$sprint_name))+.25,
127
+ y=average_velocity + 1.5,
128
+ label="Trend Line") +
129
+ # average velocity line
130
+ geom_hline(yintercept=average_velocity,linetype = "dotted", color = "#00A878") +
131
+ annotate(geom="text", x=.75, y=average_velocity + 1.5, label="Average\nVelocity") +
132
+ # plot labels
133
+ labs(title=chartTitle, x ="", y = "Story Points", fill='Outcomes',
134
+ subtitle = 'Adjusting delivered to include external carry over') +
135
+ #colors
136
+ scale_colour_manual(values = outcomeColors) +
137
+ scale_fill_manual(values = outcomeFills) +
138
+ #themes
139
+ theme(
140
+ plot.title = element_text(family = "roboto"),
141
+ axis.title.x = element_text(family = "roboto"),
142
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
143
+ axis.title.y = element_text(family = "roboto"),
144
+ panel.grid.major.y = element_line(colour = "#D9D9D9"), panel.grid.minor.y = element_line(colour = "#D9D9D9"),
145
+ panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(),
146
+ # background color
147
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
148
+ legend.position = "bottom",
149
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
150
+ )
151
+
152
+ # plot # commented out for docker
153
+
154
+ ggsave(str_c("~/projects/sprint-tracker/",str_replace(unique(outcomes$project_name)," ",""),"-carryover-adjusted",".png"),plot=plot,bg="white", width = 2882, height = 1700, units = "px")
@@ -0,0 +1,73 @@
1
+ library(tidyverse)
2
+ library(ggplot2)
3
+
4
+ # jq -r '.[]| [.id,.fields."System.Title",.fields."System.WorkItemType",.fields."System.IterationPath",.fields."System.State", input_filename] | @csv' feature-team-*.json > ~/projects/featureTeamBacklog.csv
5
+
6
+ backlog = read_csv('~/projects/featureTeamBacklog.csv', col_names = c('id','title','type','iteration','state','filename') )
7
+
8
+ backlog <- backlog %>%
9
+ tibble() %>%
10
+ mutate(
11
+ date_string = str_replace(str_replace(filename,'feature-team-',''),'.json',''),
12
+ date_string2 = str_replace(date_string,'T',' '),
13
+ date = as_datetime(date_string2,format = '%Y-%m-%d %H:%M%z')
14
+ ) %>%
15
+ select(-c("date_string","date_string2","filename"))
16
+
17
+ # Backlog work by day
18
+ # backlog %>%
19
+ # filter(! type %in% c('Feature','Task','Idea')) %>%
20
+ # filter(state == 'Backlog') %>%
21
+ # group_by(date,state,iteration) %>%
22
+ # summarize(
23
+ # count = n()
24
+ # ) %>%
25
+ # arrange(desc(date))%>%
26
+ # # Fill in empty days as zeros
27
+ # ggplot(aes(x=date,y=count)) +
28
+ # geom_line() +
29
+ # scale_x_datetime(date_labels = "%d-%b",date_breaks = "1 week")
30
+
31
+
32
+
33
+
34
+
35
+ # Work in backlog not assigned to a sprint
36
+ p = backlog %>%
37
+ filter(! type %in% c('Feature','Task','Idea')) %>%
38
+ filter(state == 'Backlog') %>%
39
+ filter(iteration == 'The Helo Project') %>%
40
+ group_by(date,state,iteration) %>%
41
+ summarize(
42
+ count = n()
43
+ ) %>%
44
+ arrange(desc(date)) %>%
45
+ ggplot(aes(x=date,y=count,fill=iteration,color=iteration)) +
46
+ geom_col() +
47
+ scale_x_datetime(date_labels = "%d-%b",date_breaks = "1 day") +
48
+ labs(title='Feature Team Backlog Items not Assigned a Sprint',
49
+ # subtitle = 'Excluding the Data Science, Product Ownership and QA Teams',
50
+ # zero means no data, either nothing in the backlog or no data recorded
51
+ x ="",
52
+ y = "Work Item Count",
53
+ fill='Iteration',
54
+ color ='Iteration') +
55
+ theme(
56
+ plot.title = element_text(family = "roboto"),
57
+ axis.title.x = element_text(family = "roboto"),
58
+ axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
59
+ axis.title.y = element_text(family = "roboto"),
60
+ panel.grid.major.y = element_line(colour = "#D9D9D9"), panel.grid.minor.y = element_line(colour = "#D9D9D9"),
61
+ panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(),
62
+ # background color
63
+ panel.background = element_rect(fill = "#D9D9D920", colour = NA),
64
+ legend.position = "bottom",
65
+ plot.background = element_rect(colour = "#D9D9D9", fill=NA)
66
+ )
67
+
68
+ FILENAME = str_c("~/projects/sprint-tracker/","featureTeamBacklog",".png")
69
+ ggsave(FILENAME,plot = p, bg="white", width = 2882, height = 1700, units = "px")
70
+ print(str_c("Saving to ",FILENAME),sep = " ")
71
+
72
+
73
+
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: "Project Health"
3
+ author: "Beau Raines"
4
+ date: "`r Sys.Date()`"
5
+ output: html_document
6
+ ---
7
+
8
+ ```{r setup, include=FALSE}
9
+ knitr::opts_chunk$set(echo = TRUE)
10
+
11
+ library(tidyverse)
12
+ library(ggplot2)
13
+ library(DBI)
14
+ library(RSQLite)
15
+
16
+
17
+ con <- dbConnect(RSQLite::SQLite(),"~/projects/sprint-tracker/tracker.db")
18
+ res <- dbSendQuery(con, "select * from health where project = 'ACDC';")
19
+ # Fetch query results
20
+ healthData <- dbFetch(res)
21
+ # Clear the result
22
+ dbClearResult(res)
23
+ # Disconnect from the database
24
+ dbDisconnect(con)
25
+
26
+ # healthData <- read_csv("~/projects/sprint-tracker/health.csv")
27
+
28
+ healthData <- healthData %>% mutate(
29
+ date = as.Date(date),
30
+ workable_issues = open_count - blocked_count
31
+ ) %>%
32
+ pivot_longer(cols = c(bug_count,unestimated_count,blocked_count,open_count,total_count,workable_issues),
33
+ values_to = "count")
34
+
35
+
36
+ ```
37
+
38
+ ## Overview
39
+
40
+ History of project health metrics over time. This data wasn't captured from the project onset
41
+
42
+ ```{r overall, echo=FALSE, fig.width=10, fig.height=3, fig.fullwidth=TRUE}
43
+
44
+ overallPlot <- healthData %>% filter(date >= Sys.Date()-15) %>%
45
+ ggplot(aes(x=date,
46
+ y=count,
47
+ color = name)) +
48
+ geom_line()
49
+
50
+ overallPlot
51
+
52
+ ```
53
+
54
+ ## Seven Day Changes
55
+
56
+ ```{r recent_changes, echo=FALSE}
57
+
58
+
59
+ latest_data = healthData %>% filter(date == max(date))
60
+ seven_days_ago = healthData %>% filter(date == max(date)-7)
61
+
62
+ tableData = bind_rows(latest_data,seven_days_ago) %>%
63
+ pivot_wider(values_from = count)
64
+
65
+ knitr::kable(tableData %>% select(-id,-project))
66
+
67
+
68
+ ```
69
+
70
+ ```{r 7 Day Changes, echo=FALSE}
71
+
72
+
73
+ bugChange = latest_data %>%
74
+ filter (name == "bug_count") %>%
75
+ pull(count) -
76
+ seven_days_ago %>%
77
+ filter (name == "bug_count") %>%
78
+ pull(count)
79
+
80
+ print(str_c("Bug delta ",bugChange))
81
+
82
+ blockedChange = latest_data %>%
83
+ filter (name == "blocked_count") %>%
84
+ pull(count) -
85
+ seven_days_ago %>%
86
+ filter (name == "blocked_count") %>%
87
+ pull(count)
88
+
89
+ print(str_c("Blocked delta ",blockedChange))
90
+
91
+ unestimatedChange = latest_data %>%
92
+ filter (name == "unestimated_count") %>%
93
+ pull(count) -
94
+ seven_days_ago %>%
95
+ filter (name == "unestimated_count") %>%
96
+ pull(count)
97
+
98
+ print(str_c("Unestimated delta ",unestimatedChange))
99
+
100
+
101
+ workableChange = latest_data %>%
102
+ filter (name == "workable_issues") %>%
103
+ pull(count) -
104
+ seven_days_ago %>%
105
+ filter (name == "workable_issues") %>%
106
+ pull(count)
107
+
108
+ print(str_c("Workable delta ",workableChange))
109
+
110
+
111
+ ```
@@ -0,0 +1,142 @@
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 = "Stories",
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
+