Commit 40e30112 authored by Mike Greiling's avatar Mike Greiling

Merge branch 'psi-burndown-charts-vue' into 'master'

Convert Burndown charts to gitlab-ui component

See merge request gitlab-org/gitlab!15463
parents 2b607f09 6c220a80
<script>
import { GlButton, GlButtonGroup } from '@gitlab/ui';
import { GlLineChart } from '@gitlab/ui/charts';
import dateFormat from 'dateformat';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
import { s__, __, sprintf } from '~/locale';
export default {
components: {
GlButton,
GlButtonGroup,
GlLineChart,
ResizableChartContainer,
},
props: {
startDate: {
type: String,
required: true,
},
dueDate: {
type: String,
required: true,
},
openIssuesCount: {
type: Array,
required: false,
default: () => [],
},
openIssuesWeight: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
issuesSelected: true,
tooltip: {
title: '',
content: '',
},
};
},
computed: {
dataSeries() {
let name;
let data;
if (this.issuesSelected) {
name = s__('BurndownChartLabel|Open issues');
data = this.openIssuesCount;
} else {
name = s__('BurndownChartLabel|Open issue weight');
data = this.openIssuesWeight;
}
const series = [
{
name,
data: data.map(d => [new Date(d[0]), d[1]]),
},
];
if (series[0] && series[0].data.length >= 2) {
const idealStart = [new Date(this.startDate), data[0][1]];
const idealEnd = [new Date(this.dueDate), 0];
const idealData = [idealStart, idealEnd];
series.push({
name: __('Guideline'),
data: idealData,
silent: true,
symbolSize: 0,
lineStyle: {
color: '#ddd',
type: 'dashed',
},
});
}
return series;
},
options() {
return {
xAxis: {
name: '',
type: 'time',
axisLine: {
show: true,
},
},
yAxis: {
name: this.issuesSelected ? __('Total issues') : __('Total weight'),
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');
if (this.issuesSelected) {
this.tooltip.content = sprintf(__('%{total} open issues'), {
total: seriesData.value[1],
});
} else {
this.tooltip.content = sprintf(__('%{total} open issue weight'), {
total: seriesData.value[1],
});
}
},
showIssueCount() {
this.issuesSelected = true;
},
showIssueWeight() {
this.issuesSelected = false;
},
},
};
</script>
<template>
<div data-qa-selector="burndown_chart">
<div class="burndown-header d-flex align-items-center">
<h3>{{ __('Burndown chart') }}</h3>
<gl-button-group class="ml-3 js-burndown-data-selector">
<gl-button
ref="totalIssuesButton"
:variant="issuesSelected ? 'primary' : 'inverted-primary'"
size="sm"
@click="showIssueCount"
>
{{ __('Issues') }}
</gl-button>
<gl-button
ref="totalWeightButton"
:variant="issuesSelected ? 'inverted-primary' : 'primary'"
size="sm"
data-qa-selector="weight_button"
@click="showIssueWeight"
>
{{ __('Issue weight') }}
</gl-button>
</gl-button-group>
</div>
<resizable-chart-container class="burndown-chart">
<gl-line-chart
slot-scope="{ width }"
:width="width"
: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>
import Vue from 'vue';
import $ from 'jquery';
import Cookies from 'js-cookie';
import BurndownChart from './burndown_chart';
import BurndownChart from './components/burndown_chart.vue';
import BurndownChartData from './burndown_chart_data';
import Flash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__, __ } from '~/locale';
import { __ } from '~/locale';
export default () => {
// handle hint dismissal
......@@ -32,44 +33,22 @@ export default () => {
const openIssuesCount = chartData.map(d => [d[0], d[1]]);
const openIssuesWeight = chartData.map(d => [d[0], d[2]]);
const chart = new BurndownChart({ container, startDate, dueDate });
let currentView = 'count';
chart.setData(openIssuesCount, {
label: s__('BurndownChartLabel|Open issues'),
animate: true,
});
$('.js-burndown-data-selector').on('click', 'button', function switchData() {
const $this = $(this);
const show = $this.data('show');
if (currentView !== show) {
currentView = show;
$this
.removeClass('btn-inverted')
.siblings()
.addClass('btn-inverted');
switch (show) {
case 'count':
chart.setData(openIssuesCount, {
label: s__('BurndownChartLabel|Open issues'),
animate: true,
});
break;
case 'weight':
chart.setData(openIssuesWeight, {
label: s__('BurndownChartLabel|Open issue weight'),
animate: true,
});
break;
default:
break;
}
}
return new Vue({
el: container,
components: {
BurndownChart,
},
render(createElement) {
return createElement('burndown-chart', {
props: {
startDate,
dueDate,
openIssuesCount,
openIssuesWeight,
},
});
},
});
window.addEventListener('resize', () => chart.animateResize(1));
$(document).on('click', '.js-sidebar-toggle', () => chart.animateResize(2));
})
.catch(() => new Flash(__('Error loading burndown chart data')));
}
......
......@@ -63,17 +63,8 @@
.burndown-chart {
width: 100%;
height: 380px;
margin: 5px 0;
@include media-breakpoint-down(sm) {
height: 320px;
}
@include media-breakpoint-down(xs) {
height: 200px;
}
.axis {
font-size: 12px;
......
......@@ -6,14 +6,6 @@
= warning
- if can_generate_chart?(milestone, burndown)
.burndown-header
%h3
Burndown chart
.btn-group.js-burndown-data-selector
%button.btn.btn-sm.btn-primary{ data: { show: 'count' } }
Issues
%button.btn.btn-sm.btn-primary.btn-inverted{ data: { show: 'weight', qa_selector: 'weight_button' } }
Issue weight
.burndown-chart{ data: { start_date: burndown.start_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' } }
......
---
title: Style burndown charts with gitlab-ui
merge_request: 15463
author:
type: changed
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe 'Burndown charts' do
describe 'Burndown charts', :js do
let(:current_user) { create(:user) }
let(:milestone) do
create(:milestone, project: project,
......
import { shallowMount } from '@vue/test-utils';
import BurndownChart from 'ee/burndown_chart/components/burndown_chart.vue';
describe('burndown_chart', () => {
let wrapper;
const issuesButton = () => wrapper.find({ ref: 'totalIssuesButton' });
const weightButton = () => wrapper.find({ ref: 'totalWeightButton' });
const defaultProps = {
startDate: '2019-08-07T00:00:00.000Z',
dueDate: '2019-09-09T00:00:00.000Z',
openIssuesCount: [],
openIssuesWeight: [],
};
const createComponent = (props = {}) => {
wrapper = shallowMount(BurndownChart, {
propsData: {
...defaultProps,
...props,
},
});
};
it('inclues Issues and Issue weight buttons', () => {
createComponent();
expect(issuesButton().text()).toBe('Issues');
expect(weightButton().text()).toBe('Issue weight');
});
it('defaults to total issues', () => {
createComponent();
expect(issuesButton().attributes('variant')).toBe('primary');
expect(weightButton().attributes('variant')).toBe('inverted-primary');
});
it('toggles Issue weight', () => {
createComponent();
weightButton().vm.$emit('click');
expect(issuesButton().attributes('variant')).toBe('inverted-primary');
expect(weightButton().attributes('variant')).toBe('primary');
});
describe('with single point', () => {
it('does not show guideline', () => {
createComponent({
openIssuesCount: [{ '2019-08-07T00:00:00.000Z': 100 }],
});
const data = wrapper.vm.dataSeries;
expect(data.length).toBe(1);
expect(data[0].name).not.toBe('Guideline');
});
});
describe('with multiple points', () => {
it('shows guideline', () => {
createComponent({
openIssuesCount: [
{ '2019-08-07T00:00:00.000Z': 100 },
{ '2019-08-08T00:00:00.000Z': 99 },
{ '2019-09-08T00:00:00.000Z': 1 },
],
});
const data = wrapper.vm.dataSeries;
expect(data.length).toBe(2);
expect(data[1].name).toBe('Guideline');
});
});
});
......@@ -375,6 +375,12 @@ msgstr ""
msgid "%{title} changes"
msgstr ""
msgid "%{total} open issue weight"
msgstr ""
msgid "%{total} open issues"
msgstr ""
msgid "%{unstaged} unstaged and %{staged} staged changes"
msgstr ""
......@@ -2649,7 +2655,7 @@ msgstr ""
msgid "Built-in"
msgstr ""
msgid "BurndownChartLabel|Guideline"
msgid "Burndown chart"
msgstr ""
msgid "BurndownChartLabel|Open issue weight"
......@@ -2658,12 +2664,6 @@ msgstr ""
msgid "BurndownChartLabel|Open issues"
msgstr ""
msgid "BurndownChartLabel|Progress"
msgstr ""
msgid "BurndownChartLabel|Remaining"
msgstr ""
msgid "Business"
msgstr ""
......@@ -8303,6 +8303,9 @@ msgstr ""
msgid "GroupsTree|Search by name"
msgstr ""
msgid "Guideline"
msgstr ""
msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
msgstr ""
......@@ -8943,6 +8946,9 @@ msgstr ""
msgid "Issue was closed by %{name} %{reason}"
msgstr ""
msgid "Issue weight"
msgstr ""
msgid "IssueBoards|Board"
msgstr ""
......@@ -17062,9 +17068,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
msgid "Total issues"
msgstr ""
msgid "Total test time for all commits/merges"
msgstr ""
msgid "Total weight"
msgstr ""
msgid "Total: %{total}"
msgstr ""
......
......@@ -8,13 +8,17 @@ module QA
class Show < ::QA::Page::Base
view 'ee/app/views/shared/milestones/_burndown.html.haml' do
element :burndown_chart
element :weight_button
end
view 'ee/app/views/shared/milestones/_weight.html.haml' do
element :total_issue_weight_value
end
view 'ee/app/assets/javascripts/burndown_chart/components/burndown_chart.vue' do
element :burndown_chart
element :weight_button
end
def click_weight_button
click_element(:weight_button)
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment