Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
61616728
Commit
61616728
authored
May 15, 2020
by
Kushal Pandya
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'psi-burnup' into 'master'
Add burnup chart FE See merge request gitlab-org/gitlab!29652
parents
54f0fd8c
b281d49c
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
681 additions
and
20 deletions
+681
-20
ee/app/assets/javascripts/burndown_chart/burn_chart_data.js
ee/app/assets/javascripts/burndown_chart/burn_chart_data.js
+56
-5
ee/app/assets/javascripts/burndown_chart/components/burn_charts.vue
...ets/javascripts/burndown_chart/components/burn_charts.vue
+13
-0
ee/app/assets/javascripts/burndown_chart/components/burndown_chart.vue
.../javascripts/burndown_chart/components/burndown_chart.vue
+6
-4
ee/app/assets/javascripts/burndown_chart/components/burnup_chart.vue
...ts/javascripts/burndown_chart/components/burnup_chart.vue
+100
-0
ee/app/assets/javascripts/burndown_chart/index.js
ee/app/assets/javascripts/burndown_chart/index.js
+31
-10
ee/app/views/shared/milestones/_burndown.html.haml
ee/app/views/shared/milestones/_burndown.html.haml
+3
-1
ee/spec/features/burnup_charts_spec.rb
ee/spec/features/burnup_charts_spec.rb
+77
-0
ee/spec/frontend/burndown_chart/burn_chart_data_spec.js
ee/spec/frontend/burndown_chart/burn_chart_data_spec.js
+351
-0
ee/spec/frontend/burndown_chart/components/burnup_chart_spec.js
...c/frontend/burndown_chart/components/burnup_chart_spec.js
+41
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
No files found.
ee/app/assets/javascripts/burndown_chart/burn
down
_chart_data.js
→
ee/app/assets/javascripts/burndown_chart/burn_chart_data.js
View file @
61616728
import
dateFormat
from
'
dateformat
'
;
import
dateFormat
from
'
dateformat
'
;
export
default
class
Burn
down
ChartData
{
export
default
class
BurnChartData
{
constructor
(
burndownE
vents
,
startDate
,
dueDate
)
{
constructor
(
e
vents
,
startDate
,
dueDate
)
{
this
.
dateFormatMask
=
'
yyyy-mm-dd
'
;
this
.
dateFormatMask
=
'
yyyy-mm-dd
'
;
this
.
startDate
=
startDate
;
this
.
startDate
=
startDate
;
this
.
dueDate
=
dueDate
;
this
.
dueDate
=
dueDate
;
this
.
burndownEvents
=
this
.
processRawEvents
(
burndownEvents
);
// determine when to stop burndown chart
// determine when to stop burndown chart
const
today
=
dateFormat
(
Date
.
now
(),
this
.
dateFormatMask
);
const
today
=
dateFormat
(
Date
.
now
(),
this
.
dateFormatMask
);
...
@@ -15,11 +14,63 @@ export default class BurndownChartData {
...
@@ -15,11 +14,63 @@ export default class BurndownChartData {
// and dateFormat() both convert the date at midnight UTC to the browser's
// and dateFormat() both convert the date at midnight UTC to the browser's
// timezone, leading to incorrect chart start and end points. Using
// timezone, leading to incorrect chart start and end points. Using
// new Date('YYYY-MM-DDTHH:MM:SS') gets the user's local date at midnight.
// new Date('YYYY-MM-DDTHH:MM:SS') gets the user's local date at midnight.
this
.
localStartDate
=
new
Date
(
`
${
this
.
startDate
}
T00:00:00`
);
this
.
localStartDate
=
new
Date
(
`
${
this
.
startDate
}
T00:00:00`
);
this
.
localEndDate
=
new
Date
(
`
${
this
.
endDate
}
T00:00:00`
);
this
.
localEndDate
=
new
Date
(
`
${
this
.
endDate
}
T00:00:00`
);
this
.
events
=
this
.
processRawEvents
(
events
);
}
generateBurnupTimeseries
({
milestoneId
}
=
{})
{
const
chartData
=
[];
let
openIssuesCount
=
0
;
let
carriedIssuesCount
=
0
;
for
(
let
date
=
this
.
localStartDate
;
date
<=
this
.
localEndDate
;
date
.
setDate
(
date
.
getDate
()
+
1
)
)
{
const
dateString
=
dateFormat
(
date
,
this
.
dateFormatMask
);
const
openedIssuesToday
=
this
.
filterAndSummarizeBurndownEvents
(
event
=>
event
.
created_at
===
dateString
&&
event
.
event_type
===
'
milestone
'
&&
event
.
milestone_id
===
milestoneId
&&
event
.
action
===
'
add
'
,
);
const
closedIssuesToday
=
this
.
filterAndSummarizeBurndownEvents
(
event
=>
event
.
created_at
===
dateString
&&
event
.
event_type
===
'
milestone
'
&&
((
event
.
action
===
'
remove
'
&&
event
.
milestone_id
===
milestoneId
)
||
(
event
.
action
===
'
add
'
&&
event
.
milestone_id
!==
milestoneId
)),
);
openIssuesCount
+=
openedIssuesToday
.
count
-
closedIssuesToday
.
count
;
if
(
openIssuesCount
+
carriedIssuesCount
<
0
)
{
carriedIssuesCount
+=
openIssuesCount
;
openIssuesCount
=
0
;
}
else
{
openIssuesCount
+=
carriedIssuesCount
;
carriedIssuesCount
=
0
;
}
chartData
.
push
([
dateString
,
openIssuesCount
]);
}
return
{
burnupScope
:
chartData
,
};
}
}
generate
()
{
generate
BurndownTimeseries
()
{
let
openIssuesCount
=
0
;
let
openIssuesCount
=
0
;
let
openIssuesWeight
=
0
;
let
openIssuesWeight
=
0
;
...
@@ -87,7 +138,7 @@ export default class BurndownChartData {
...
@@ -87,7 +138,7 @@ export default class BurndownChartData {
}
}
filterAndSummarizeBurndownEvents
(
filter
)
{
filterAndSummarizeBurndownEvents
(
filter
)
{
const
issues
=
this
.
burndownE
vents
.
filter
(
filter
);
const
issues
=
this
.
e
vents
.
filter
(
filter
);
return
{
return
{
count
:
issues
.
length
,
count
:
issues
.
length
,
...
...
ee/app/assets/javascripts/burndown_chart/components/burn_charts.vue
View file @
61616728
...
@@ -3,12 +3,14 @@ import { GlButton, GlButtonGroup } from '@gitlab/ui';
...
@@ -3,12 +3,14 @@ import { GlButton, GlButtonGroup } from '@gitlab/ui';
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
BurndownChart
from
'
./burndown_chart.vue
'
;
import
BurndownChart
from
'
./burndown_chart.vue
'
;
import
BurnupChart
from
'
./burnup_chart.vue
'
;
export
default
{
export
default
{
components
:
{
components
:
{
GlButton
,
GlButton
,
GlButtonGroup
,
GlButtonGroup
,
BurndownChart
,
BurndownChart
,
BurnupChart
,
},
},
mixins
:
[
glFeatureFlagsMixin
()],
mixins
:
[
glFeatureFlagsMixin
()],
props
:
{
props
:
{
...
@@ -30,6 +32,11 @@ export default {
...
@@ -30,6 +32,11 @@ export default {
required
:
false
,
required
:
false
,
default
:
()
=>
[],
default
:
()
=>
[],
},
},
burnupScope
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
},
data
()
{
data
()
{
return
{
return
{
...
@@ -90,6 +97,12 @@ export default {
...
@@ -90,6 +97,12 @@ export default {
:issues-selected=
"issuesSelected"
:issues-selected=
"issuesSelected"
class=
"col-md-6"
class=
"col-md-6"
/>
/>
<burnup-chart
:start-date=
"startDate"
:due-date=
"dueDate"
:scope=
"burnupScope"
class=
"col-md-6"
/>
</div>
</div>
<burndown-chart
<burndown-chart
v-else
v-else
...
...
ee/app/assets/javascripts/burndown_chart/components/burndown_chart.vue
View file @
61616728
...
@@ -63,13 +63,13 @@ export default {
...
@@ -63,13 +63,13 @@ export default {
const
series
=
[
const
series
=
[
{
{
name
,
name
,
data
:
data
.
map
(
d
=>
[
new
Date
(
d
[
0
]),
d
[
1
]])
,
data
,
},
},
];
];
if
(
series
[
0
]
&&
series
[
0
].
data
.
length
>=
2
)
{
if
(
series
[
0
]
&&
series
[
0
].
data
.
length
>=
2
)
{
const
idealStart
=
[
new
Date
(
this
.
startDate
)
,
data
[
0
][
1
]];
const
idealStart
=
[
this
.
startDate
,
data
[
0
][
1
]];
const
idealEnd
=
[
new
Date
(
this
.
dueDate
)
,
0
];
const
idealEnd
=
[
this
.
dueDate
,
0
];
const
idealData
=
[
idealStart
,
idealEnd
];
const
idealData
=
[
idealStart
,
idealEnd
];
series
.
push
({
series
.
push
({
...
@@ -91,6 +91,8 @@ export default {
...
@@ -91,6 +91,8 @@ export default {
xAxis
:
{
xAxis
:
{
name
:
''
,
name
:
''
,
type
:
'
time
'
,
type
:
'
time
'
,
min
:
this
.
startDate
,
max
:
this
.
dueDate
,
axisLine
:
{
axisLine
:
{
show
:
true
,
show
:
true
,
},
},
...
@@ -141,7 +143,7 @@ export default {
...
@@ -141,7 +143,7 @@ export default {
<div
v-if=
"showTitle"
class=
"burndown-header d-flex align-items-center"
>
<div
v-if=
"showTitle"
class=
"burndown-header d-flex align-items-center"
>
<h3>
{{
__
(
'
Burndown chart
'
)
}}
</h3>
<h3>
{{
__
(
'
Burndown chart
'
)
}}
</h3>
</div>
</div>
<resizable-chart-container
class=
"burndown-chart"
>
<resizable-chart-container
class=
"burndown-chart
js-burndown-chart
"
>
<gl-line-chart
<gl-line-chart
slot-scope=
"
{ width }"
slot-scope=
"
{ width }"
:width="width"
:width="width"
...
...
ee/app/assets/javascripts/burndown_chart/components/burnup_chart.vue
0 → 100644
View file @
61616728
<
script
>
import
{
GlLineChart
}
from
'
@gitlab/ui/dist/charts
'
;
import
dateFormat
from
'
dateformat
'
;
import
ResizableChartContainer
from
'
~/vue_shared/components/resizable_chart/resizable_chart_container.vue
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
export
default
{
components
:
{
GlLineChart
,
ResizableChartContainer
,
},
props
:
{
startDate
:
{
type
:
String
,
required
:
true
,
},
dueDate
:
{
type
:
String
,
required
:
true
,
},
scope
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
data
()
{
return
{
tooltip
:
{
title
:
''
,
content
:
''
,
},
};
},
computed
:
{
dataSeries
()
{
const
series
=
[
{
name
:
__
(
'
Total
'
),
data
:
this
.
scope
,
},
];
return
series
;
},
options
()
{
return
{
xAxis
:
{
name
:
''
,
type
:
'
time
'
,
min
:
this
.
startDate
,
max
:
this
.
dueDate
,
axisLine
:
{
show
:
true
,
},
},
yAxis
:
{
name
:
__
(
'
Total issues
'
),
axisLine
:
{
show
:
true
,
},
splitLine
:
{
show
:
false
,
},
},
tooltip
:
{
trigger
:
'
item
'
,
formatter
:
()
=>
''
,
},
};
},
},
methods
:
{
formatTooltipText
(
params
)
{
const
[
seriesData
]
=
params
.
seriesData
;
this
.
tooltip
.
title
=
dateFormat
(
params
.
value
,
'
dd mmm yyyy
'
);
const
text
=
__
(
'
%{total} open issues
'
);
this
.
tooltip
.
content
=
sprintf
(
text
,
{
total
:
seriesData
.
value
[
1
],
});
},
},
};
</
script
>
<
template
>
<div
data-qa-selector=
"burnup_chart"
>
<div
class=
"burndown-header d-flex align-items-center"
>
<h3>
{{
__
(
'
Burnup chart
'
)
}}
</h3>
</div>
<resizable-chart-container
class=
"js-burnup-chart"
>
<gl-line-chart
:data=
"dataSeries"
:option=
"options"
:format-tooltip-text=
"formatTooltipText"
>
<template
slot=
"tooltipTitle"
>
{{
tooltip
.
title
}}
</
template
>
<
template
slot=
"tooltipContent"
>
{{
tooltip
.
content
}}
</
template
>
</gl-line-chart>
</resizable-chart-container>
</div>
</template>
ee/app/assets/javascripts/burndown_chart/index.js
View file @
61616728
...
@@ -2,8 +2,8 @@ import Vue from 'vue';
...
@@ -2,8 +2,8 @@ import Vue from 'vue';
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
Cookies
from
'
js-cookie
'
;
import
Cookies
from
'
js-cookie
'
;
import
BurnCharts
from
'
./components/burn_charts.vue
'
;
import
BurnCharts
from
'
./components/burn_charts.vue
'
;
import
BurndownChartData
from
'
./burn
down
_chart_data
'
;
import
BurndownChartData
from
'
./burn_chart_data
'
;
import
Flash
from
'
~/flash
'
;
import
create
Flash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
...
@@ -22,16 +22,34 @@ export default () => {
...
@@ -22,16 +22,34 @@ export default () => {
if
(
$chartEl
.
length
)
{
if
(
$chartEl
.
length
)
{
const
startDate
=
$chartEl
.
data
(
'
startDate
'
);
const
startDate
=
$chartEl
.
data
(
'
startDate
'
);
const
dueDate
=
$chartEl
.
data
(
'
dueDate
'
);
const
dueDate
=
$chartEl
.
data
(
'
dueDate
'
);
const
milestoneId
=
$chartEl
.
data
(
'
milestoneId
'
);
const
burndownEventsPath
=
$chartEl
.
data
(
'
burndownEventsPath
'
);
const
burndownEventsPath
=
$chartEl
.
data
(
'
burndownEventsPath
'
);
const
burnupEventsPath
=
$chartEl
.
data
(
'
burnupEventsPath
'
);
axios
const
fetchData
=
[
axios
.
get
(
burndownEventsPath
)];
.
get
(
burndownEventsPath
)
.
then
(
response
=>
{
const
burndownEvents
=
response
.
data
;
const
chartData
=
new
BurndownChartData
(
burndownEvents
,
startDate
,
dueDate
).
generate
();
const
openIssuesCount
=
chartData
.
map
(
d
=>
[
d
[
0
],
d
[
1
]]);
if
(
gon
.
features
.
burnupCharts
)
{
const
openIssuesWeight
=
chartData
.
map
(
d
=>
[
d
[
0
],
d
[
2
]]);
fetchData
.
push
(
axios
.
get
(
burnupEventsPath
));
}
Promise
.
all
(
fetchData
)
.
then
(([
burndownResponse
,
burnupResponse
])
=>
{
const
burndownEvents
=
burndownResponse
.
data
;
const
burndownChartData
=
new
BurndownChartData
(
burndownEvents
,
startDate
,
dueDate
,
).
generateBurndownTimeseries
();
const
burnupEvents
=
burnupResponse
?.
data
||
[];
const
{
burnupScope
}
=
new
BurndownChartData
(
burnupEvents
,
startDate
,
dueDate
).
generateBurnupTimeseries
({
milestoneId
,
})
||
{};
const
openIssuesCount
=
burndownChartData
.
map
(
d
=>
[
d
[
0
],
d
[
1
]]);
const
openIssuesWeight
=
burndownChartData
.
map
(
d
=>
[
d
[
0
],
d
[
2
]]);
return
new
Vue
({
return
new
Vue
({
el
:
container
,
el
:
container
,
...
@@ -45,11 +63,14 @@ export default () => {
...
@@ -45,11 +63,14 @@ export default () => {
dueDate
,
dueDate
,
openIssuesCount
,
openIssuesCount
,
openIssuesWeight
,
openIssuesWeight
,
burnupScope
,
},
},
});
});
},
},
});
});
})
})
.
catch
(()
=>
new
Flash
(
__
(
'
Error loading burndown chart data
'
)));
.
catch
(()
=>
{
createFlash
(
__
(
'
Error loading burndown chart data
'
));
});
}
}
};
};
ee/app/views/shared/milestones/_burndown.html.haml
View file @
61616728
...
@@ -2,13 +2,15 @@
...
@@ -2,13 +2,15 @@
-
burndown
=
burndown_chart
(
milestone
)
-
burndown
=
burndown_chart
(
milestone
)
-
warning
=
data_warning_for
(
burndown
)
-
warning
=
data_warning_for
(
burndown
)
-
burndown_endpoint
=
milestone
.
group_milestone?
?
api_v4_groups_milestones_burndown_events_path
(
id:
milestone
.
group
.
id
,
milestone_id:
milestone
.
id
)
:
api_v4_projects_milestones_burndown_events_path
(
id:
milestone
.
project
.
id
,
milestone_id:
milestone
.
timebox_id
)
-
burndown_endpoint
=
milestone
.
group_milestone?
?
api_v4_groups_milestones_burndown_events_path
(
id:
milestone
.
group
.
id
,
milestone_id:
milestone
.
id
)
:
api_v4_projects_milestones_burndown_events_path
(
id:
milestone
.
project
.
id
,
milestone_id:
milestone
.
timebox_id
)
-
burnup_endpoint
=
milestone
.
group_milestone?
?
api_v4_groups_milestones_burnup_events_path
(
id:
milestone
.
group
.
id
,
milestone_id:
milestone
.
id
)
:
api_v4_projects_milestones_burnup_events_path
(
id:
milestone
.
project
.
id
,
milestone_id:
milestone
.
timebox_id
)
=
warning
=
warning
-
if
can_generate_chart?
(
milestone
,
burndown
)
-
if
can_generate_chart?
(
milestone
,
burndown
)
.burndown-chart.mb-2
{
data:
{
start_date:
burndown
.
start_date
.
strftime
(
"%Y-%m-%d"
),
.burndown-chart.mb-2
{
data:
{
start_date:
burndown
.
start_date
.
strftime
(
"%Y-%m-%d"
),
due_date:
burndown
.
due_date
.
strftime
(
"%Y-%m-%d"
),
due_date:
burndown
.
due_date
.
strftime
(
"%Y-%m-%d"
),
burndown_events_path:
expose_url
(
burndown_endpoint
),
qa_selector:
'burndown_chart'
}
}
milestone_id:
milestone
.
id
,
burndown_events_path:
expose_url
(
burndown_endpoint
),
burnup_events_path:
expose_url
(
burnup_endpoint
),
qa_selector:
'burndown_chart'
}
}
-
elsif
show_burndown_placeholder?
(
milestone
,
warning
)
-
elsif
show_burndown_placeholder?
(
milestone
,
warning
)
.burndown-hint.content-block.container-fluid
.burndown-hint.content-block.container-fluid
...
...
ee/spec/features/burnup_charts_spec.rb
0 → 100644
View file @
61616728
# frozen_string_literal: true
require
'spec_helper'
describe
'Burnup charts'
,
:js
do
let_it_be
(
:burnup_chart_selector
)
{
'.js-burnup-chart'
}
let_it_be
(
:burndown_chart_selector
)
{
'.js-burndown-chart'
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:project
)
{
create
(
:project
,
namespace:
group
)
}
let_it_be
(
:milestone
)
{
create
(
:milestone
,
:with_dates
,
group:
group
,
title:
'January Milestone'
,
description:
'Cut scope from milestone'
,
start_date:
'2020-01-30'
,
due_date:
'2020-02-10'
)
}
let_it_be
(
:other_milestone
)
{
create
(
:milestone
,
:with_dates
,
group:
group
,
title:
'February Milestone'
,
description:
'burnup sample'
,
start_date:
'2020-02-11'
,
due_date:
'2020-02-28'
)
}
let_it_be
(
:issue_1
)
{
create
(
:issue
,
created_at:
'2020-01-30'
,
project:
project
,
milestone:
milestone
,
weight:
2
)
}
let_it_be
(
:issue_2
)
{
create
(
:issue
,
created_at:
'2020-01-30'
,
project:
project
,
milestone:
milestone
,
weight:
3
)
}
let_it_be
(
:issue_3
)
{
create
(
:issue
,
created_at:
'2020-01-30'
,
project:
project
,
milestone:
milestone
,
weight:
2
)
}
let_it_be
(
:event1
)
{
create
(
:resource_milestone_event
,
issue:
issue_1
,
milestone:
milestone
,
action:
'add'
,
created_at:
'2020-01-30'
)
}
let_it_be
(
:event2
)
{
create
(
:resource_milestone_event
,
issue:
issue_2
,
milestone:
milestone
,
action:
'add'
,
created_at:
'2020-01-30'
)
}
let_it_be
(
:event3
)
{
create
(
:resource_milestone_event
,
issue:
issue_3
,
milestone:
milestone
,
action:
'add'
,
created_at:
'2020-01-30'
)
}
let_it_be
(
:event4
)
{
create
(
:resource_milestone_event
,
issue:
issue_2
,
milestone:
milestone
,
action:
'remove'
,
created_at:
'2020-02-06'
)
}
let_it_be
(
:event5
)
{
create
(
:resource_milestone_event
,
issue:
issue_3
,
milestone:
other_milestone
,
action:
'add'
,
created_at:
'2020-02-06'
)
}
before
do
group
.
add_developer
(
user
)
sign_in
(
user
)
end
describe
'licensed feature available'
do
before
do
stub_licensed_features
(
group_burndown_charts:
true
)
end
it
'shows burnup chart, with a point per day'
do
visit
group_milestone_path
(
milestone
.
group
,
milestone
)
expect
(
burnup_chart_points
.
count
).
to
be
(
12
)
end
end
describe
'licensed feature not available'
do
before
do
stub_licensed_features
(
group_burndown_charts:
false
)
end
it
'does not show burnup chart'
do
visit
group_milestone_path
(
milestone
.
group
,
milestone
)
expect
(
page
).
not_to
have_selector
(
burnup_chart_selector
)
end
end
describe
'feature flag disabled'
do
before
do
stub_licensed_features
(
group_burndown_charts:
true
)
stub_feature_flags
(
burnup_charts:
false
)
end
it
'only shows burndown chart'
do
visit
group_milestone_path
(
milestone
.
group
,
milestone
)
expect
(
page
).
to
have_selector
(
burndown_chart_selector
)
expect
(
page
).
not_to
have_selector
(
burnup_chart_selector
)
end
end
def
burnup_chart_points
fill_color
=
"#5772ff"
burnup_chart
.
all
(
"path[fill='
#{
fill_color
}
']"
,
count:
12
)
end
def
burnup_chart
page
.
find
(
burnup_chart_selector
)
end
end
ee/spec/frontend/burndown_chart/burn
down
_chart_data_spec.js
→
ee/spec/frontend/burndown_chart/burn_chart_data_spec.js
View file @
61616728
import
dateFormat
from
'
dateformat
'
;
import
dateFormat
from
'
dateformat
'
;
import
timezoneMock
from
'
timezone-mock
'
;
import
timezoneMock
from
'
timezone-mock
'
;
import
BurndownChartData
from
'
ee/burndown_chart/burn
down
_chart_data
'
;
import
BurndownChartData
from
'
ee/burndown_chart/burn_chart_data
'
;
describe
(
'
BurndownChartData
'
,
()
=>
{
describe
(
'
BurndownChartData
'
,
()
=>
{
const
startDate
=
'
2017-03-01
'
;
const
startDate
=
'
2017-03-01
'
;
const
dueDate
=
'
2017-03-03
'
;
const
dueDate
=
'
2017-03-03
'
;
const
mileston
eEvents
=
[
const
issueStat
eEvents
=
[
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
...
@@ -16,15 +16,15 @@ describe('BurndownChartData', () => {
...
@@ -16,15 +16,15 @@ describe('BurndownChartData', () => {
{
created_at
:
'
2017-03-03T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
reopened
'
},
{
created_at
:
'
2017-03-03T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
reopened
'
},
];
];
let
burndownChartData
;
describe
(
'
generateBurndownTimeseries
'
,
()
=>
{
let
burndownChartData
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
burndownChartData
=
new
BurndownChartData
(
mileston
eEvents
,
startDate
,
dueDate
);
burndownChartData
=
new
BurndownChartData
(
issueStat
eEvents
,
startDate
,
dueDate
);
});
});
describe
(
'
generate
'
,
()
=>
{
it
(
'
generates an array of arrays with date, issue count and weight
'
,
()
=>
{
it
(
'
generates an array of arrays with date, issue count and weight
'
,
()
=>
{
expect
(
burndownChartData
.
generate
()).
toEqual
([
expect
(
burndownChartData
.
generate
BurndownTimeseries
()).
toEqual
([
[
'
2017-03-01
'
,
2
,
4
],
[
'
2017-03-01
'
,
2
,
4
],
[
'
2017-03-02
'
,
1
,
2
],
[
'
2017-03-02
'
,
1
,
2
],
[
'
2017-03-03
'
,
3
,
6
],
[
'
2017-03-03
'
,
3
,
6
],
...
@@ -41,7 +41,7 @@ describe('BurndownChartData', () => {
...
@@ -41,7 +41,7 @@ describe('BurndownChartData', () => {
});
});
it
(
'
has the right start and end dates
'
,
()
=>
{
it
(
'
has the right start and end dates
'
,
()
=>
{
expect
(
burndownChartData
.
generate
()).
toEqual
([
expect
(
burndownChartData
.
generate
BurndownTimeseries
()).
toEqual
([
[
'
2017-03-01
'
,
1
,
2
],
[
'
2017-03-01
'
,
1
,
2
],
[
'
2017-03-02
'
,
3
,
6
],
[
'
2017-03-02
'
,
3
,
6
],
[
'
2017-03-03
'
,
3
,
6
],
[
'
2017-03-03
'
,
3
,
6
],
...
@@ -51,7 +51,7 @@ describe('BurndownChartData', () => {
...
@@ -51,7 +51,7 @@ describe('BurndownChartData', () => {
describe
(
'
when issues are created before start date
'
,
()
=>
{
describe
(
'
when issues are created before start date
'
,
()
=>
{
beforeAll
(()
=>
{
beforeAll
(()
=>
{
mileston
eEvents
.
push
({
issueStat
eEvents
.
push
({
created_at
:
'
2017-02-28T00:00:00.000Z
'
,
created_at
:
'
2017-02-28T00:00:00.000Z
'
,
weight
:
2
,
weight
:
2
,
action
:
'
created
'
,
action
:
'
created
'
,
...
@@ -59,7 +59,7 @@ describe('BurndownChartData', () => {
...
@@ -59,7 +59,7 @@ describe('BurndownChartData', () => {
});
});
it
(
'
generates an array of arrays with date, issue count and weight
'
,
()
=>
{
it
(
'
generates an array of arrays with date, issue count and weight
'
,
()
=>
{
expect
(
burndownChartData
.
generate
()).
toEqual
([
expect
(
burndownChartData
.
generate
BurndownTimeseries
()).
toEqual
([
[
'
2017-03-01
'
,
3
,
6
],
[
'
2017-03-01
'
,
3
,
6
],
[
'
2017-03-02
'
,
2
,
4
],
[
'
2017-03-02
'
,
2
,
4
],
[
'
2017-03-03
'
,
4
,
8
],
[
'
2017-03-03
'
,
4
,
8
],
...
@@ -80,7 +80,7 @@ describe('BurndownChartData', () => {
...
@@ -80,7 +80,7 @@ describe('BurndownChartData', () => {
});
});
it
(
'
counts until today if milestone due date > date today
'
,
()
=>
{
it
(
'
counts until today if milestone due date > date today
'
,
()
=>
{
const
chartData
=
burndownChartData
.
generate
();
const
chartData
=
burndownChartData
.
generate
BurndownTimeseries
();
expect
(
dateFormat
(
Date
.
now
(),
'
yyyy-mm-dd
'
)).
toEqual
(
'
2017-03-02
'
);
expect
(
dateFormat
(
Date
.
now
(),
'
yyyy-mm-dd
'
)).
toEqual
(
'
2017-03-02
'
);
expect
(
chartData
[
chartData
.
length
-
1
][
0
]).
toEqual
(
'
2017-03-02
'
);
expect
(
chartData
[
chartData
.
length
-
1
][
0
]).
toEqual
(
'
2017-03-02
'
);
...
@@ -90,8 +90,8 @@ describe('BurndownChartData', () => {
...
@@ -90,8 +90,8 @@ describe('BurndownChartData', () => {
describe
(
'
when days in milestone have negative counts
'
,
()
=>
{
describe
(
'
when days in milestone have negative counts
'
,
()
=>
{
describe
(
'
and the first two days have a negative count
'
,
()
=>
{
describe
(
'
and the first two days have a negative count
'
,
()
=>
{
beforeAll
(()
=>
{
beforeAll
(()
=>
{
mileston
eEvents
.
length
=
0
;
issueStat
eEvents
.
length
=
0
;
mileston
eEvents
.
push
(
issueStat
eEvents
.
push
(
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
...
@@ -101,7 +101,7 @@ describe('BurndownChartData', () => {
...
@@ -101,7 +101,7 @@ describe('BurndownChartData', () => {
});
});
it
(
'
generates an array where the first two days counts are zero
'
,
()
=>
{
it
(
'
generates an array where the first two days counts are zero
'
,
()
=>
{
expect
(
burndownChartData
.
generate
()).
toEqual
([
expect
(
burndownChartData
.
generate
BurndownTimeseries
()).
toEqual
([
[
'
2017-03-01
'
,
0
,
0
],
[
'
2017-03-01
'
,
0
,
0
],
[
'
2017-03-02
'
,
0
,
0
],
[
'
2017-03-02
'
,
0
,
0
],
[
'
2017-03-03
'
,
1
,
2
],
[
'
2017-03-03
'
,
1
,
2
],
...
@@ -115,8 +115,8 @@ describe('BurndownChartData', () => {
...
@@ -115,8 +115,8 @@ describe('BurndownChartData', () => {
// potential edge case.
// potential edge case.
beforeAll
(()
=>
{
beforeAll
(()
=>
{
mileston
eEvents
.
length
=
0
;
issueStat
eEvents
.
length
=
0
;
mileston
eEvents
.
push
(
issueStat
eEvents
.
push
(
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
...
@@ -126,7 +126,7 @@ describe('BurndownChartData', () => {
...
@@ -126,7 +126,7 @@ describe('BurndownChartData', () => {
});
});
it
(
'
generates an array where the middle day count is zero
'
,
()
=>
{
it
(
'
generates an array where the middle day count is zero
'
,
()
=>
{
expect
(
burndownChartData
.
generate
()).
toEqual
([
expect
(
burndownChartData
.
generate
BurndownTimeseries
()).
toEqual
([
[
'
2017-03-01
'
,
1
,
2
],
[
'
2017-03-01
'
,
1
,
2
],
[
'
2017-03-02
'
,
0
,
0
],
[
'
2017-03-02
'
,
0
,
0
],
[
'
2017-03-03
'
,
1
,
2
],
[
'
2017-03-03
'
,
1
,
2
],
...
@@ -140,8 +140,8 @@ describe('BurndownChartData', () => {
...
@@ -140,8 +140,8 @@ describe('BurndownChartData', () => {
// potential edge case.
// potential edge case.
beforeAll
(()
=>
{
beforeAll
(()
=>
{
mileston
eEvents
.
length
=
0
;
issueStat
eEvents
.
length
=
0
;
mileston
eEvents
.
push
(
issueStat
eEvents
.
push
(
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
created
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
weight
:
2
,
action
:
'
closed
'
},
...
@@ -151,7 +151,7 @@ describe('BurndownChartData', () => {
...
@@ -151,7 +151,7 @@ describe('BurndownChartData', () => {
});
});
it
(
'
generates an array where all counts are zero
'
,
()
=>
{
it
(
'
generates an array where all counts are zero
'
,
()
=>
{
expect
(
burndownChartData
.
generate
()).
toEqual
([
expect
(
burndownChartData
.
generate
BurndownTimeseries
()).
toEqual
([
[
'
2017-03-01
'
,
0
,
0
],
[
'
2017-03-01
'
,
0
,
0
],
[
'
2017-03-02
'
,
0
,
0
],
[
'
2017-03-02
'
,
0
,
0
],
[
'
2017-03-03
'
,
0
,
0
],
[
'
2017-03-03
'
,
0
,
0
],
...
@@ -160,4 +160,192 @@ describe('BurndownChartData', () => {
...
@@ -160,4 +160,192 @@ describe('BurndownChartData', () => {
});
});
});
});
});
});
describe
(
'
generateBurnupTimeseries
'
,
()
=>
{
const
milestoneId
=
400
;
const
milestoneEvents
=
[
// day 1: add two issues to the milestone
{
action
:
'
add
'
,
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
1
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
{
action
:
'
add
'
,
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
2
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
// day 2: remove both issues we added yesterday, add a different issue
{
action
:
'
remove
'
,
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
2
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
{
action
:
'
add
'
,
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
3
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
{
action
:
'
remove
'
,
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
1
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
// day 3: remove yesterday's issue, also remove an issue that didn't have an `add` event
{
action
:
'
remove
'
,
created_at
:
'
2017-03-03T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
2
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
{
action
:
'
remove
'
,
created_at
:
'
2017-03-03T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
4
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
];
const
burndownChartData
=
(
events
=
milestoneEvents
)
=>
{
return
new
BurndownChartData
(
events
,
startDate
,
dueDate
);
};
it
(
'
generates an array of arrays with date and issue count
'
,
()
=>
{
const
{
burnupScope
}
=
burndownChartData
().
generateBurnupTimeseries
({
milestoneId
});
expect
(
burnupScope
).
toEqual
([[
'
2017-03-01
'
,
2
],
[
'
2017-03-02
'
,
1
],
[
'
2017-03-03
'
,
0
]]);
});
it
(
'
starts from 0
'
,
()
=>
{
const
{
burnupScope
}
=
burndownChartData
([]).
generateBurnupTimeseries
({
milestoneId
,
});
expect
(
burnupScope
[
0
]).
toEqual
([
'
2017-03-01
'
,
0
],
[
'
2017-03-01
'
,
0
],
[
'
2017-03-01
'
,
0
]);
});
it
(
'
does not go below zero with extra remove events
'
,
()
=>
{
const
{
burnupScope
}
=
burndownChartData
([
{
action
:
'
remove
'
,
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
2
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
{
action
:
'
remove
'
,
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
event_type
:
'
milestone
'
,
issue_id
:
1
,
milestone_id
:
milestoneId
,
weight
:
null
,
},
]).
generateBurnupTimeseries
({
milestoneId
,
});
expect
(
burnupScope
).
toEqual
([[
'
2017-03-01
'
,
0
],
[
'
2017-03-02
'
,
0
],
[
'
2017-03-03
'
,
0
]]);
});
it
(
'
ignores removed from other milestones
'
,
()
=>
{
const
differentMilestoneId
=
600
;
const
events
=
[
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
action
:
'
add
'
,
event_type
:
'
milestone
'
,
milestone_id
:
milestoneId
,
issue_id
:
1
,
},
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
action
:
'
remove
'
,
event_type
:
'
milestone
'
,
milestone_id
:
differentMilestoneId
,
issue_id
:
1
,
},
];
const
{
burnupScope
}
=
burndownChartData
(
events
).
generateBurnupTimeseries
({
milestoneId
});
expect
(
burnupScope
).
toEqual
([[
'
2017-03-01
'
,
1
],
[
'
2017-03-02
'
,
1
],
[
'
2017-03-03
'
,
1
]]);
});
it
(
'
only adds milestone event_type
'
,
()
=>
{
const
events
=
[
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
action
:
'
add
'
,
event_type
:
'
weight
'
,
milestone_id
:
milestoneId
,
issue_id
:
1
,
weight
:
2
,
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
action
:
'
add
'
,
event_type
:
'
milestone
'
,
milestone_id
:
milestoneId
,
issue_id
:
1
,
weight
:
null
,
},
];
const
{
burnupScope
}
=
burndownChartData
(
events
).
generateBurnupTimeseries
({
milestoneId
});
expect
(
burnupScope
).
toEqual
([[
'
2017-03-01
'
,
0
],
[
'
2017-03-02
'
,
1
],
[
'
2017-03-03
'
,
1
]]);
});
it
(
'
only removes milestone event_type
'
,
()
=>
{
const
events
=
[
{
created_at
:
'
2017-03-01T00:00:00.000Z
'
,
action
:
'
add
'
,
event_type
:
'
milestone
'
,
milestone_id
:
milestoneId
,
issue_id
:
1
,
},
{
created_at
:
'
2017-03-02T00:00:00.000Z
'
,
action
:
'
remove
'
,
event_type
:
'
weight
'
,
milestone_id
:
milestoneId
,
issue_id
:
1
,
weight
:
2
,
},
{
created_at
:
'
2017-03-03T00:00:00.000Z
'
,
action
:
'
remove
'
,
event_type
:
'
milestone
'
,
milestone_id
:
milestoneId
,
issue_id
:
1
,
},
];
const
{
burnupScope
}
=
burndownChartData
(
events
).
generateBurnupTimeseries
({
milestoneId
});
expect
(
burnupScope
).
toEqual
([[
'
2017-03-01
'
,
1
],
[
'
2017-03-02
'
,
1
],
[
'
2017-03-03
'
,
0
]]);
});
});
});
});
ee/spec/frontend/burndown_chart/components/burnup_chart_spec.js
0 → 100644
View file @
61616728
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlLineChart
}
from
'
@gitlab/ui/dist/charts
'
;
import
ResizableChartContainer
from
'
~/vue_shared/components/resizable_chart/resizable_chart_container.vue
'
;
import
BurnupChart
from
'
ee/burndown_chart/components/burnup_chart.vue
'
;
describe
(
'
Burnup chart
'
,
()
=>
{
let
wrapper
;
const
defaultProps
=
{
startDate
:
'
2019-08-07T00:00:00.000Z
'
,
dueDate
:
'
2019-09-09T00:00:00.000Z
'
,
};
const
createComponent
=
(
props
=
{})
=>
{
wrapper
=
shallowMount
(
BurnupChart
,
{
propsData
:
{
...
defaultProps
,
...
props
,
},
stubs
:
{
ResizableChartContainer
,
},
});
};
it
.
each
`
scope
${[{
'
2019-08-07T00:00:00.000Z
'
:
100
}]}
${[{
'
2019-08-07T00:00:00.000Z
'
:
100
},
{
'
2019-08-08T00:00:00.000Z
'
:
99
},
{
'
2019-09-08T00:00:00.000Z
'
:
1
}]}
`
(
'
renders the lineChart correctly
'
,
({
scope
})
=>
{
createComponent
({
scope
});
const
chartData
=
wrapper
.
find
(
GlLineChart
).
props
(
'
data
'
);
expect
(
chartData
).
toEqual
([
{
name
:
'
Total
'
,
data
:
scope
,
},
]);
});
});
locale/gitlab.pot
View file @
61616728
...
@@ -3506,6 +3506,9 @@ msgstr ""
...
@@ -3506,6 +3506,9 @@ msgstr ""
msgid "BurndownChartLabel|Open issues"
msgid "BurndownChartLabel|Open issues"
msgstr ""
msgstr ""
msgid "Burnup chart"
msgstr ""
msgid "Business"
msgid "Business"
msgstr ""
msgstr ""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment