Commit a9720f23 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Minor refactor, move chart into component

Refactors the chart component into a
separate file. Also fixes the
feature specs.

Fix stub license feature call
parent 7d2e8db6
<script>
import { GlEmptyState, GlDaterangePicker, GlLoadingIcon } from '@gitlab/ui';
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts';
import { mapActions, mapState, mapGetters } from 'vuex';
import dateFormat from 'dateformat';
import { s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
......@@ -14,14 +11,14 @@ import Scatterplot from '../../shared/components/scatterplot.vue';
import StageDropdownFilter from './stage_dropdown_filter.vue';
import SummaryTable from './summary_table.vue';
import StageTable from './stage_table.vue';
import { LAST_ACTIVITY_AT, dateFormats } from '../../shared/constants';
import { LAST_ACTIVITY_AT } from '../../shared/constants';
import TasksByTypeChart from './tasks_by_type_chart.vue';
export default {
name: 'CycleAnalytics',
components: {
GlLoadingIcon,
GlEmptyState,
GlStackedColumnChart,
GroupsDropdownFilter,
ProjectsDropdownFilter,
SummaryTable,
......@@ -29,6 +26,7 @@ export default {
GlDaterangePicker,
StageDropdownFilter,
Scatterplot,
TasksByTypeChart,
},
mixins: [glFeatureFlagsMixin()],
props: {
......@@ -71,6 +69,7 @@ export default {
'isCreatingCustomStage',
'isEditingCustomStage',
'selectedGroup',
'selectedProjectIds',
'selectedStage',
'stages',
'summary',
......@@ -103,12 +102,7 @@ export default {
return !this.isLoadingDurationChart && !this.isLoading;
},
shouldDisplayTasksByTypeChart() {
return (
!this.isLoadingTasksByTypeChart &&
!this.isLoading &&
this.tasksByTypeChartData &&
this.tasksByTypeChartData.seriesData
);
return !this.isLoadingTasksByTypeChart && !this.isLoading && this.tasksByTypeChartData;
},
dateRange: {
get() {
......@@ -124,27 +118,22 @@ export default {
hasDateRangeSet() {
return this.startDate && this.endDate;
},
chartDataDescription() {
if (this.selectedGroup) {
const selectedProjectCount = this.setSelectedProjects.length;
const { startDate, endDate } = this;
const { name: groupName } = this.selectedGroup;
const str =
selectedProjectCount > 0
? s__(
"CycleAnalyticsCharts|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}",
)
: s__(
"CycleAnalyticsCharts|Showing data for group '%{groupName}' from %{startDate} to %{endDate}",
);
return sprintf(str, {
startDate: dateFormat(startDate, dateFormats.defaultDate),
endDate: dateFormat(endDate, dateFormats.defaultDate),
groupName,
selectedProjectCount,
});
}
return null;
selectedTasksByTypeFilters() {
const {
selectedGroup,
startDate,
endDate,
selectedProjectIds,
tasksByType: { subject, labelIds: selectedLabelIds },
} = this;
return {
selectedGroup,
selectedProjectIds,
startDate,
endDate,
subject,
selectedLabelIds,
};
},
},
mounted() {
......@@ -153,6 +142,9 @@ export default {
hasDurationChart: this.glFeatures.cycleAnalyticsScatterplotEnabled,
hasTasksByTypeChart: this.glFeatures.tasksByTypeChart,
});
console.log('mounted::this.glFeatures', this.glFeatures);
console.log('mounted::this.featureFlags', this.featureFlags);
},
methods: {
...mapActions([
......@@ -219,9 +211,6 @@ export default {
with_shared: false,
order_by: LAST_ACTIVITY_AT,
},
tasksByTypeChartOptions: {
legend: false,
},
};
</script>
......@@ -342,41 +331,31 @@ export default {
</template>
<gl-loading-icon v-else-if="!isLoading" size="md" class="my-4 py-4" />
</template>
</div>
<template v-if="featureFlags.hasTasksByTypeChart">
<div v-if="shouldDisplayTasksByTypeChart">
<!-- TODO: move into component file -->
<div class="row">
<div class="col-12">
<h2>{{ __('Type of work') }}</h2>
<p v-if="tasksByTypeChartData">
{{ __('Showing data for __ groups and __ projects from __ to __') }}
</p>
</div>
</div>
<div v-if="tasksByTypeChartData" class="row">
<div class="col-12">
<header>
<h3>{{ __('Tasks by type') }}</h3>
</header>
<!-- TODO: no data available view -->
<section>
<gl-stacked-column-chart
:option="$options.tasksByTypeChartOptions"
:data="tasksByTypeChartData.seriesData"
:group-by="tasksByTypeChartData.range"
x-axis-type="category"
x-axis-title="Date"
y-axis-title="Number of tasks"
:series-names="tasksByTypeChartData.seriesNames"
/>
</section>
<template v-if="featureFlags.hasTasksByTypeChart">
<div v-if="!isLoading">
<gl-loading-icon v-if="isLoadingTasksByTypeChart" size="md" class="my-4 py-4" />
<div v-else class="tasks-by-type-chart">
<tasks-by-type-chart
v-if="shouldDisplayTasksByTypeChart"
:data="tasksByTypeChartData.seriesData"
:group-by="tasksByTypeChartData.range"
:series-names="tasksByTypeChartData.seriesNames"
:filters="selectedTasksByTypeFilters"
/>
<div v-else>
<!-- TODO: move this inside the component -->
<div class="row">
<div class="col-12">
<h3>{{ __('Type of work') }}</h3>
<div class="bs-callout bs-callout-info">
<p>{{ __('There is no data available. Please change your selection.') }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="bs-callout bs-callout-info">
{{ __('There is no data available. Please change your selection.') }}
</div>
</div>
</template>
</template>
</div>
</div>
</template>
<script>
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { s__, sprintf } from '~/locale';
import { dateFormats } from '../../shared/constants';
const formattedDate = d => dateFormat(d, dateFormats.defaultDate);
export default {
name: 'TasksByTypeChart',
components: {
GlStackedColumnChart,
},
props: {
filters: {
type: Object,
required: true,
},
data: {
type: Array,
required: true,
},
groupBy: {
type: Array,
required: true,
},
seriesNames: {
type: Array,
required: true,
},
},
computed: {
hasData() {
return this.data && this.data.length;
},
selectedFiltersText() {
// TODO: I think we should show labels that have 0 data, currently doesnt appear
const { subject, selectedLabelIds } = this.filters;
return sprintf(
s__('CycleAnalyticsCharts|Showing %{subject} and %{selectedLabelsCount} labels'),
{
subject,
selectedLabelsCount: selectedLabelIds.length,
},
);
},
summaryDescription() {
const {
startDate,
endDate,
selectedProjectIds,
selectedGroup: { name: groupName },
} = this.filters;
const selectedProjectCount = selectedProjectIds.length;
const str =
selectedProjectCount > 0
? s__(
"CycleAnalyticsCharts|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}",
)
: s__(
"CycleAnalyticsCharts|Showing data for group '%{groupName}' from %{startDate} to %{endDate}",
);
return sprintf(str, {
startDate: formattedDate(startDate),
endDate: formattedDate(endDate),
groupName,
selectedProjectCount,
});
},
},
chartOptions: {
legend: false,
},
};
</script>
<template>
<div>
<div class="row">
<div class="col-12">
<h3>{{ __('Type of work') }}</h3>
<p v-if="hasData">
{{ summaryDescription }}
</p>
</div>
</div>
<div v-if="hasData" class="row">
<div class="col-12">
<header>
<h4>{{ __('Tasks by type') }}</h4>
<p>{{ selectedFiltersText }}</p>
</header>
<section>
<gl-stacked-column-chart
:option="$options.chartOptions"
:data="data"
:group-by="groupBy"
x-axis-type="category"
x-axis-title="Date"
y-axis-title="Number of tasks"
:series-names="seriesNames"
/>
</section>
</div>
</div>
</div>
</template>
......@@ -131,6 +131,8 @@ export const editCustomStage = ({ commit, dispatch }, selectedStage = {}) => {
export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_DATA);
export const receiveSummaryDataError = ({ commit }, error) => {
console.log('receiveSummaryDataError::error', error);
commit(types.RECEIVE_SUMMARY_DATA_ERROR, error);
createFlash(__('There was an error while fetching cycle analytics summary data.'));
};
......@@ -246,10 +248,13 @@ export const createCustomStage = ({ dispatch, state }, data) => {
.catch(error => dispatch('receiveCreateCustomStageError', { error, data }));
};
export const receiveTasksByTypeDataSuccess = ({ commit }, data) =>
export const receiveTasksByTypeDataSuccess = ({ commit }, data) => {
console.log('receiveTasksByTypeDataSuccess::data', data);
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, data);
};
export const receiveTasksByTypeDataError = ({ commit }, error) => {
console.log('receiveTasksByTypeDataError::error', error);
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error);
createFlash(__('There was an error fetching data for the tasks by type chart'));
};
......
......@@ -35,5 +35,5 @@ export const tasksByTypeChartData = ({ tasksByType, startDate, endDate }) => {
endDate,
});
}
return {};
return null;
};
......@@ -78,12 +78,11 @@ export const transformRawStages = (stages = []) =>
name: name.length ? name : title,
}));
export const arrayToObject = (arr = []) => {
return arr.reduce((acc, curr) => {
export const arrayToObject = (arr = []) =>
arr.reduce((acc, curr) => {
const [key, value] = curr;
return { ...acc, [key]: value };
}, {});
};
// converts the series data into key value pairs
export const transformRawTasksByTypeData = (data = []) => {
......
......@@ -9,6 +9,8 @@ describe 'Group Cycle Analytics', :js do
let(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
let(:label) { create(:group_label, group: group) }
let(:label2) { create(:group_label, group: group) }
stage_nav_selector = '.stage-nav'
......@@ -18,6 +20,7 @@ describe 'Group Cycle Analytics', :js do
before do
stub_licensed_features(cycle_analytics_for_groups: true)
group.add_owner(user)
project.add_maintainer(user)
......@@ -217,6 +220,67 @@ describe 'Group Cycle Analytics', :js do
end
end
describe 'Tasks by type chart', :js do
context 'enabled' do
before do
stub_licensed_features(cycle_analytics_for_groups: true, type_of_work_analytics: true)
sign_in(user)
end
context 'with data available' do
before do
3.times do |i|
create(:labeled_issue, created_at: i.days.ago, project: create(:project, group: group), labels: [label])
create(:labeled_issue, created_at: i.days.ago, project: create(:project, group: group), labels: [label2])
end
visit analytics_cycle_analytics_path
select_group
end
it 'displays the chart' do
expect(page).to have_text('Type of work')
expect(page).to have_text('Tasks by type')
end
it 'has 2 labels selected' do
expect(page).to have_text('Showing Issue and 2 labels')
end
end
context 'no data available' do
before do
visit analytics_cycle_analytics_path
select_group
end
it 'shows the no data available message' do
expect(page).to have_text('Type of work')
expect(page).to have_text('There is no data available. Please change your selection.')
end
end
end
context 'not enabled' do
before do
stub_feature_flags(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG => false)
visit analytics_cycle_analytics_path
select_group
end
it 'will not display the tasks by type chart' do
expect(page).not_to have_selector('.tasks-by-type-chart')
expect(page).not_to have_text('Tasks by type')
end
end
end
describe 'Customizable cycle analytics', :js do
custom_stage_name = "Cool beans"
start_event_identifier = :merge_request_created
......
......@@ -30,7 +30,6 @@ import {
endDate,
issueStage,
rawCustomStage,
tasksByTypeData,
transformedTasksByTypeData,
} from './mock_data';
......
......@@ -219,7 +219,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
sign_in(user)
end
it 'analytics/type_of_work/tasks_by_type?created_before=":created_before"' do
it 'analytics/type_of_work/tasks_by_type.json' do
params = { group_id: group.full_path, label_ids: [label.id, label2.id, label3.id], created_after: 10.days.ago, subject: 'Issue' }
get(:show, params: params, format: :json)
......
......@@ -2480,9 +2480,6 @@ msgstr ""
msgid "Background color"
msgstr ""
msgid "Backstage"
msgstr ""
msgid "Badges"
msgstr ""
......@@ -2900,9 +2897,6 @@ msgstr ""
msgid "Browse files"
msgstr ""
msgid "Bug"
msgstr ""
msgid "BuildArtifacts|An error occurred while fetching the artifacts"
msgstr ""
......@@ -5544,7 +5538,7 @@ msgstr ""
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
msgid "CycleAnalyticsEvent|Issue closed"
msgid "CycleAnalyticsCharts|Showing %{subject} and %{selectedLabelsCount} labels"
msgstr ""
msgid "CycleAnalyticsCharts|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}"
......@@ -5553,6 +5547,9 @@ msgstr ""
msgid "CycleAnalyticsCharts|Showing data for group '%{groupName}' from %{startDate} to %{endDate}"
msgstr ""
msgid "CycleAnalyticsEvent|Issue closed"
msgstr ""
msgid "CycleAnalyticsEvent|Issue created"
msgstr ""
......@@ -7782,9 +7779,6 @@ msgstr ""
msgid "Favicon was successfully removed."
msgstr ""
msgid "Feature"
msgstr ""
msgid "Feature Flags"
msgstr ""
......@@ -19548,10 +19542,10 @@ msgstr ""
msgid "Type"
msgstr ""
msgid "Type/State"
msgid "Type of work"
msgstr ""
msgid "Type of work"
msgid "Type/State"
msgstr ""
msgid "U2F Devices (%{length})"
......
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