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> <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 dateFormat from 'dateformat';
import { s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility'; import { getDateInPast } 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';
...@@ -14,14 +11,14 @@ import Scatterplot from '../../shared/components/scatterplot.vue'; ...@@ -14,14 +11,14 @@ import Scatterplot from '../../shared/components/scatterplot.vue';
import StageDropdownFilter from './stage_dropdown_filter.vue'; 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, dateFormats } from '../../shared/constants'; import { LAST_ACTIVITY_AT } from '../../shared/constants';
import TasksByTypeChart from './tasks_by_type_chart.vue';
export default { export default {
name: 'CycleAnalytics', name: 'CycleAnalytics',
components: { components: {
GlLoadingIcon, GlLoadingIcon,
GlEmptyState, GlEmptyState,
GlStackedColumnChart,
GroupsDropdownFilter, GroupsDropdownFilter,
ProjectsDropdownFilter, ProjectsDropdownFilter,
SummaryTable, SummaryTable,
...@@ -29,6 +26,7 @@ export default { ...@@ -29,6 +26,7 @@ export default {
GlDaterangePicker, GlDaterangePicker,
StageDropdownFilter, StageDropdownFilter,
Scatterplot, Scatterplot,
TasksByTypeChart,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
props: { props: {
...@@ -71,6 +69,7 @@ export default { ...@@ -71,6 +69,7 @@ export default {
'isCreatingCustomStage', 'isCreatingCustomStage',
'isEditingCustomStage', 'isEditingCustomStage',
'selectedGroup', 'selectedGroup',
'selectedProjectIds',
'selectedStage', 'selectedStage',
'stages', 'stages',
'summary', 'summary',
...@@ -103,12 +102,7 @@ export default { ...@@ -103,12 +102,7 @@ export default {
return !this.isLoadingDurationChart && !this.isLoading; return !this.isLoadingDurationChart && !this.isLoading;
}, },
shouldDisplayTasksByTypeChart() { shouldDisplayTasksByTypeChart() {
return ( return !this.isLoadingTasksByTypeChart && !this.isLoading && this.tasksByTypeChartData;
!this.isLoadingTasksByTypeChart &&
!this.isLoading &&
this.tasksByTypeChartData &&
this.tasksByTypeChartData.seriesData
);
}, },
dateRange: { dateRange: {
get() { get() {
...@@ -124,27 +118,22 @@ export default { ...@@ -124,27 +118,22 @@ export default {
hasDateRangeSet() { hasDateRangeSet() {
return this.startDate && this.endDate; return this.startDate && this.endDate;
}, },
chartDataDescription() { selectedTasksByTypeFilters() {
if (this.selectedGroup) { const {
const selectedProjectCount = this.setSelectedProjects.length; selectedGroup,
const { startDate, endDate } = this; startDate,
const { name: groupName } = this.selectedGroup; endDate,
const str = selectedProjectIds,
selectedProjectCount > 0 tasksByType: { subject, labelIds: selectedLabelIds },
? s__( } = this;
"CycleAnalyticsCharts|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}", return {
) selectedGroup,
: s__( selectedProjectIds,
"CycleAnalyticsCharts|Showing data for group '%{groupName}' from %{startDate} to %{endDate}", startDate,
); endDate,
return sprintf(str, { subject,
startDate: dateFormat(startDate, dateFormats.defaultDate), selectedLabelIds,
endDate: dateFormat(endDate, dateFormats.defaultDate), };
groupName,
selectedProjectCount,
});
}
return null;
}, },
}, },
mounted() { mounted() {
...@@ -153,6 +142,9 @@ export default { ...@@ -153,6 +142,9 @@ export default {
hasDurationChart: this.glFeatures.cycleAnalyticsScatterplotEnabled, hasDurationChart: this.glFeatures.cycleAnalyticsScatterplotEnabled,
hasTasksByTypeChart: this.glFeatures.tasksByTypeChart, hasTasksByTypeChart: this.glFeatures.tasksByTypeChart,
}); });
console.log('mounted::this.glFeatures', this.glFeatures);
console.log('mounted::this.featureFlags', this.featureFlags);
}, },
methods: { methods: {
...mapActions([ ...mapActions([
...@@ -219,9 +211,6 @@ export default { ...@@ -219,9 +211,6 @@ export default {
with_shared: false, with_shared: false,
order_by: LAST_ACTIVITY_AT, order_by: LAST_ACTIVITY_AT,
}, },
tasksByTypeChartOptions: {
legend: false,
},
}; };
</script> </script>
...@@ -342,41 +331,31 @@ export default { ...@@ -342,41 +331,31 @@ export default {
</template> </template>
<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> <template v-if="featureFlags.hasTasksByTypeChart">
<template v-if="featureFlags.hasTasksByTypeChart"> <div v-if="!isLoading">
<div v-if="shouldDisplayTasksByTypeChart"> <gl-loading-icon v-if="isLoadingTasksByTypeChart" size="md" class="my-4 py-4" />
<!-- TODO: move into component file --> <div v-else class="tasks-by-type-chart">
<div class="row"> <tasks-by-type-chart
<div class="col-12"> v-if="shouldDisplayTasksByTypeChart"
<h2>{{ __('Type of work') }}</h2> :data="tasksByTypeChartData.seriesData"
<p v-if="tasksByTypeChartData"> :group-by="tasksByTypeChartData.range"
{{ __('Showing data for __ groups and __ projects from __ to __') }} :series-names="tasksByTypeChartData.seriesNames"
</p> :filters="selectedTasksByTypeFilters"
</div> />
</div> <div v-else>
<div v-if="tasksByTypeChartData" class="row"> <!-- TODO: move this inside the component -->
<div class="col-12"> <div class="row">
<header> <div class="col-12">
<h3>{{ __('Tasks by type') }}</h3> <h3>{{ __('Type of work') }}</h3>
</header> <div class="bs-callout bs-callout-info">
<!-- TODO: no data available view --> <p>{{ __('There is no data available. Please change your selection.') }}</p>
<section> </div>
<gl-stacked-column-chart </div>
:option="$options.tasksByTypeChartOptions" </div>
:data="tasksByTypeChartData.seriesData" </div>
:group-by="tasksByTypeChartData.range"
x-axis-type="category"
x-axis-title="Date"
y-axis-title="Number of tasks"
:series-names="tasksByTypeChartData.seriesNames"
/>
</section>
</div> </div>
</div> </div>
<div v-else class="bs-callout bs-callout-info"> </template>
{{ __('There is no data available. Please change your selection.') }} </div>
</div>
</div>
</template>
</div> </div>
</template> </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 = {}) => { ...@@ -131,6 +131,8 @@ export const editCustomStage = ({ commit, dispatch }, selectedStage = {}) => {
export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_DATA); export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_DATA);
export const receiveSummaryDataError = ({ commit }, error) => { export const receiveSummaryDataError = ({ commit }, error) => {
console.log('receiveSummaryDataError::error', error);
commit(types.RECEIVE_SUMMARY_DATA_ERROR, error); commit(types.RECEIVE_SUMMARY_DATA_ERROR, error);
createFlash(__('There was an error while fetching cycle analytics summary data.')); createFlash(__('There was an error while fetching cycle analytics summary data.'));
}; };
...@@ -246,10 +248,13 @@ export const createCustomStage = ({ dispatch, state }, data) => { ...@@ -246,10 +248,13 @@ export const createCustomStage = ({ dispatch, state }, data) => {
.catch(error => dispatch('receiveCreateCustomStageError', { error, 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); commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, data);
};
export const receiveTasksByTypeDataError = ({ commit }, error) => { export const receiveTasksByTypeDataError = ({ commit }, error) => {
console.log('receiveTasksByTypeDataError::error', error);
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error); commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error);
createFlash(__('There was an error fetching data for the tasks by type chart')); createFlash(__('There was an error fetching data for the tasks by type chart'));
}; };
......
...@@ -35,5 +35,5 @@ export const tasksByTypeChartData = ({ tasksByType, startDate, endDate }) => { ...@@ -35,5 +35,5 @@ export const tasksByTypeChartData = ({ tasksByType, startDate, endDate }) => {
endDate, endDate,
}); });
} }
return {}; return null;
}; };
...@@ -78,12 +78,11 @@ export const transformRawStages = (stages = []) => ...@@ -78,12 +78,11 @@ export const transformRawStages = (stages = []) =>
name: name.length ? name : title, name: name.length ? name : title,
})); }));
export const arrayToObject = (arr = []) => { export const arrayToObject = (arr = []) =>
return arr.reduce((acc, curr) => { arr.reduce((acc, curr) => {
const [key, value] = curr; const [key, value] = curr;
return { ...acc, [key]: value }; return { ...acc, [key]: value };
}, {}); }, {});
};
// converts the series data into key value pairs // converts the series data into key value pairs
export const transformRawTasksByTypeData = (data = []) => { export const transformRawTasksByTypeData = (data = []) => {
......
...@@ -9,6 +9,8 @@ describe 'Group Cycle Analytics', :js do ...@@ -9,6 +9,8 @@ describe 'Group Cycle Analytics', :js do
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") } 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(: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' stage_nav_selector = '.stage-nav'
...@@ -18,6 +20,7 @@ describe 'Group Cycle Analytics', :js do ...@@ -18,6 +20,7 @@ describe 'Group Cycle Analytics', :js do
before do before do
stub_licensed_features(cycle_analytics_for_groups: true) stub_licensed_features(cycle_analytics_for_groups: true)
group.add_owner(user) group.add_owner(user)
project.add_maintainer(user) project.add_maintainer(user)
...@@ -217,6 +220,67 @@ describe 'Group Cycle Analytics', :js do ...@@ -217,6 +220,67 @@ describe 'Group Cycle Analytics', :js do
end end
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 describe 'Customizable cycle analytics', :js do
custom_stage_name = "Cool beans" custom_stage_name = "Cool beans"
start_event_identifier = :merge_request_created start_event_identifier = :merge_request_created
......
...@@ -30,7 +30,6 @@ import { ...@@ -30,7 +30,6 @@ import {
endDate, endDate,
issueStage, issueStage,
rawCustomStage, rawCustomStage,
tasksByTypeData,
transformedTasksByTypeData, transformedTasksByTypeData,
} from './mock_data'; } from './mock_data';
......
...@@ -219,7 +219,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -219,7 +219,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
sign_in(user) sign_in(user)
end 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' } 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) get(:show, params: params, format: :json)
......
...@@ -2480,9 +2480,6 @@ msgstr "" ...@@ -2480,9 +2480,6 @@ msgstr ""
msgid "Background color" msgid "Background color"
msgstr "" msgstr ""
msgid "Backstage"
msgstr ""
msgid "Badges" msgid "Badges"
msgstr "" msgstr ""
...@@ -2900,9 +2897,6 @@ msgstr "" ...@@ -2900,9 +2897,6 @@ msgstr ""
msgid "Browse files" msgid "Browse files"
msgstr "" msgstr ""
msgid "Bug"
msgstr ""
msgid "BuildArtifacts|An error occurred while fetching the artifacts" msgid "BuildArtifacts|An error occurred while fetching the artifacts"
msgstr "" msgstr ""
...@@ -5544,7 +5538,7 @@ 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." msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr "" msgstr ""
msgid "CycleAnalyticsEvent|Issue closed" msgid "CycleAnalyticsCharts|Showing %{subject} and %{selectedLabelsCount} labels"
msgstr "" msgstr ""
msgid "CycleAnalyticsCharts|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}" msgid "CycleAnalyticsCharts|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}"
...@@ -5553,6 +5547,9 @@ msgstr "" ...@@ -5553,6 +5547,9 @@ msgstr ""
msgid "CycleAnalyticsCharts|Showing data for group '%{groupName}' from %{startDate} to %{endDate}" msgid "CycleAnalyticsCharts|Showing data for group '%{groupName}' from %{startDate} to %{endDate}"
msgstr "" msgstr ""
msgid "CycleAnalyticsEvent|Issue closed"
msgstr ""
msgid "CycleAnalyticsEvent|Issue created" msgid "CycleAnalyticsEvent|Issue created"
msgstr "" msgstr ""
...@@ -7782,9 +7779,6 @@ msgstr "" ...@@ -7782,9 +7779,6 @@ msgstr ""
msgid "Favicon was successfully removed." msgid "Favicon was successfully removed."
msgstr "" msgstr ""
msgid "Feature"
msgstr ""
msgid "Feature Flags" msgid "Feature Flags"
msgstr "" msgstr ""
...@@ -19548,10 +19542,10 @@ msgstr "" ...@@ -19548,10 +19542,10 @@ msgstr ""
msgid "Type" msgid "Type"
msgstr "" msgstr ""
msgid "Type/State" msgid "Type of work"
msgstr "" msgstr ""
msgid "Type of work" msgid "Type/State"
msgstr "" msgstr ""
msgid "U2F Devices (%{length})" 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