Commit 21b1e874 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Scaffold ui for type of work chart

Adds the basic chart component to
base.vue and fetches data
parent 4f642915
<script> <script>
import { GlEmptyState, GlDaterangePicker, GlLoadingIcon } from '@gitlab/ui'; import { GlEmptyState, GlDaterangePicker,GlLoadingIcon } from '@gitlab/ui';
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { getDateInPast } from '~/lib/utils/datetime_utility'; import { __ } from '~/locale';
import createFlash from '~/flash';
import { getDateInPast, getDayDifference } from '~/lib/utils/datetime_utility';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { PROJECTS_PER_PAGE, DEFAULT_DAYS_IN_PAST } from '../constants'; import { PROJECTS_PER_PAGE, DEFAULT_DAYS_IN_PAST } from '../constants';
...@@ -12,12 +15,39 @@ import StageDropdownFilter from './stage_dropdown_filter.vue'; ...@@ -12,12 +15,39 @@ import StageDropdownFilter from './stage_dropdown_filter.vue';
import SummaryTable from './summary_table.vue'; import SummaryTable from './summary_table.vue';
import StageTable from './stage_table.vue'; import StageTable from './stage_table.vue';
import { LAST_ACTIVITY_AT } from '../../shared/constants'; import { LAST_ACTIVITY_AT } from '../../shared/constants';
import { toYmd } from '../../shared/utils';
const generateDatesBetweenStartAndEnd = (start, end) => {
const dayDifference = getDayDifference(start, end);
return [...Array(dayDifference).keys()].map(i => {
const d = getDateInPast(end, i);
return toYmd(new Date(d));
});
};
const prepareDataset = ({ dataset, range }) =>
dataset.reduce(
(acc, curr) => {
const {
label: { title },
series: [datapoints],
} = curr;
acc.seriesNames = [...acc.seriesNames, title];
acc.data = [...acc.data, range.map(index => (datapoints[index] ? datapoints[index] : 0))];
return acc;
},
{ data: [], seriesNames: [] },
);
export default { export default {
name: 'CycleAnalytics', name: 'CycleAnalytics',
components: { components: {
GlLoadingIcon, GlLoadingIcon,
<<<<<<< HEAD
GlEmptyState, GlEmptyState,
=======
GlStackedColumnChart,
>>>>>>> Scaffold ui for type of work chart
GroupsDropdownFilter, GroupsDropdownFilter,
ProjectsDropdownFilter, ProjectsDropdownFilter,
SummaryTable, SummaryTable,
...@@ -45,6 +75,14 @@ export default { ...@@ -45,6 +75,14 @@ export default {
return { return {
multiProjectSelect: true, multiProjectSelect: true,
dateOptions: [7, 30, 90], dateOptions: [7, 30, 90],
groupsQueryParams: {
min_access_level: featureAccessLevel.EVERYONE,
},
projectsQueryParams: {
per_page: PROJECTS_PER_PAGE,
with_shared: false,
order_by: 'last_activity_at',
},
}; };
}, },
computed: { computed: {
...@@ -71,7 +109,7 @@ export default { ...@@ -71,7 +109,7 @@ export default {
'tasksByType', 'tasksByType',
'medians', 'medians',
]), ]),
...mapGetters(['hasNoAccessError', 'currentGroupPath', 'durationChartPlottableData']), ...mapGetters(['hasNoAccessError', 'currentGroupPath', 'durationChartPlottableData','tasksByTypeData']),
shouldRenderEmptyState() { shouldRenderEmptyState() {
return !this.selectedGroup; return !this.selectedGroup;
}, },
...@@ -95,6 +133,20 @@ export default { ...@@ -95,6 +133,20 @@ export default {
}); });
}, },
}, },
hasDateRangeSet() {
return this.startDate && this.endDate;
},
typeOfWork() {
if (!this.hasDateRangeSet) {
return { option: { legend: false }, datatset: [], range: [] };
}
const range = generateDatesBetweenStartAndEnd(this.startDate, this.endDate).reverse();
return {
option: { legend: false },
range,
...prepareDataset({ dataset: this.tasksByTypeData, range }),
};
},
}, },
mounted() { mounted() {
this.initDateRange(); this.initDateRange();
...@@ -289,5 +341,31 @@ export default { ...@@ -289,5 +341,31 @@ export default {
<gl-loading-icon v-else-if="!isLoading" size="md" class="my-4 py-4" /> <gl-loading-icon v-else-if="!isLoading" size="md" class="my-4 py-4" />
</template> </template>
</div> </div>
<div v-if="hasDateRangeSet">
<div class="row">
<div class="col-12">
<h2>{{ __('Type of work') }}</h2>
<p>{{ __('Showing data for __ groups and __ projects from __ to __') }}</p>
</div>
</div>
<div class="row">
<div class="col-6">
<header>
<h3>{{ __('Tasks by type') }}</h3>
</header>
<section>
<gl-stacked-column-chart
:option="typeOfWork.option"
:data="typeOfWork.data"
:group-by="typeOfWork.range"
x-axis-type="category"
x-axis-title="Date"
y-axis-title="Number of tasks"
:series-names="typeOfWork.seriesNames"
/>
</section>
</div>
</div>
</div>
</div> </div>
</template> </template>
// TODO: replace this test data with an endpoint
import { __ } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import { toYmd } from '../../shared/utils';
const today = new Date();
const dataRange = [...Array(30).keys()]
.map(i => {
const d = getDateInPast(today, i);
return toYmd(new Date(d));
})
.reverse();
function randomInt(range) {
return Math.floor(Math.random() * Math.floor(range));
}
function arrayToObject(arr) {
return arr.reduce((acc, curr) => {
const [key, value] = curr;
return { ...acc, [key]: value };
}, {});
}
const genSeries = () => arrayToObject(dataRange.map(key => [key, randomInt(100)]));
const fakeApiResponse = convertObjectPropsToCamelCase(
[
{
label: {
id: 1,
title: __('Bug'),
color: '#428BCA',
text_color: '#FFFFFF',
},
series: [genSeries()],
},
{
label: {
id: 3,
title: __('Backstage'),
color: '#327BCA',
text_color: '#FFFFFF',
},
series: [genSeries()],
},
{
label: {
id: 2,
title: __('Feature'),
color: '#428BCA',
text_color: '#FFFFFF',
},
series: [genSeries()],
},
],
{ deep: true },
);
const transformResponseToLabelHash = data => {};
export const typeOfWork = convertObjectPropsToCamelCase(fakeApiResponse, { deep: true });
...@@ -25,3 +25,5 @@ export const durationChartPlottableData = state => { ...@@ -25,3 +25,5 @@ export const durationChartPlottableData = state => {
return plottableData.length ? plottableData : null; return plottableData.length ? plottableData : null;
}; };
export const tasksByTypeData = state =>
state.tasksByType && state.tasksByType.data ? state.tasksByType.data : [];
...@@ -176,3 +176,5 @@ export default { ...@@ -176,3 +176,5 @@ export default {
state.isLoadingDurationChart = false; state.isLoadingDurationChart = false;
}, },
}; };
...@@ -189,3 +189,5 @@ export const getDurationChartData = (data, startDate, endDate) => { ...@@ -189,3 +189,5 @@ export const getDurationChartData = (data, startDate, endDate) => {
return eventData; return eventData;
}; };
// takes the type of work data and converts to a k:v structure
export const transformRawTypeOfWorkData = raw => {};
import dateFormat from 'dateformat';
import { dateFormats } from './constants';
export const toYmd = date => dateFormat(date, dateFormats.isoDate);
export default {
toYmd,
};
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