@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,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
|
+
|