Commit 0b2e5048 authored by Clement Ho's avatar Clement Ho

Merge branch '13076-add-stage-button-ee' into 'master'

Customizable Cycle Analytics - Add stage button

Closes #13076

See merge request gitlab-org/gitlab-ee!15122
parents 1eb24b98 8216a617
export default {
data() {
return {
isCustomStageForm: false,
};
},
methods: {
showAddStageForm: () => {},
hideAddStageForm: () => {},
},
};
...@@ -23,7 +23,10 @@ export default { ...@@ -23,7 +23,10 @@ export default {
</script> </script>
<template> <template>
<div :class="{ active: isActive }" class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded"> <div
:class="{ active: isActive }"
class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px"
>
<slot></slot> <slot></slot>
<div v-if="canEdit" class="dropdown"> <div v-if="canEdit" class="dropdown">
<gl-button <gl-button
......
...@@ -3,6 +3,7 @@ import Vue from 'vue'; ...@@ -3,6 +3,7 @@ import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins'; import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins';
import addStageMixin from 'ee_else_ce/analytics/cycle_analytics/mixins/add_stage_mixin';
import Flash from '../flash'; import Flash from '../flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
...@@ -43,8 +44,12 @@ export default () => { ...@@ -43,8 +44,12 @@ export default () => {
DateRangeDropdown: () => DateRangeDropdown: () =>
import('ee_component/analytics/shared/components/date_range_dropdown.vue'), import('ee_component/analytics/shared/components/date_range_dropdown.vue'),
'stage-nav-item': stageNavItem, 'stage-nav-item': stageNavItem,
CustomStageForm: () =>
import('ee_component/analytics/cycle_analytics/components/custom_stage_form.vue'),
AddStageButton: () =>
import('ee_component/analytics/cycle_analytics/components/add_stage_button.vue'),
}, },
mixins: [filterMixins], mixins: [filterMixins, addStageMixin],
data() { data() {
return { return {
store: CycleAnalyticsStore, store: CycleAnalyticsStore,
...@@ -124,6 +129,7 @@ export default () => { ...@@ -124,6 +129,7 @@ export default () => {
return; return;
} }
this.hideAddStageForm();
this.isLoadingStage = true; this.isLoadingStage = true;
this.store.setStageEvents([], stage); this.store.setStageEvents([], stage);
this.store.setActiveStage(stage); this.store.setActiveStage(stage);
......
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
width: 20%; width: 20%;
} }
.fa { .fa {
color: $cycle-analytics-light-gray; color: $cycle-analytics-light-gray;
...@@ -146,7 +145,6 @@ ...@@ -146,7 +145,6 @@
.stage-nav-item { .stage-nav-item {
line-height: 65px; line-height: 65px;
border: 1px solid $border-color;
&.active { &.active {
background: $blue-50; background: $blue-50;
......
...@@ -15,3 +15,9 @@ ...@@ -15,3 +15,9 @@
font-size: $size; font-size: $size;
} }
} }
.border-width-1px { border-width: 1px; }
.border-style-dashed { border-style: dashed; }
.border-style-solid { border-style: solid; }
.border-color-blue-300 { border-color: $blue-300; }
.border-color-default { border-color: $border-color; }
<script>
export default {
props: {
active: {
type: Boolean,
required: true,
},
},
computed: {
activeClass() {
return 'active font-weight-bold border-style-solid border-color-blue-300';
},
inactiveClass() {
return 'bg-transparent border-style-dashed border-color-default';
},
},
};
</script>
<template>
<li
:class="[active ? activeClass : inactiveClass]"
class="js-add-stage-button stage-nav-item ml-2 mb-1 rounded d-flex justify-content-center border-width-1px"
@click="$emit('showform')"
>
{{ s__('CustomCycleAnalytics|Add a stage') }}
</li>
</template>
<template>
<div class="ml-3">
<h2>{{ s__('New stage') }}</h2>
</div>
</template>
export default {
data() {
return {
isCustomStageForm: false,
};
},
methods: {
showAddStageForm() {
if (this.store) {
this.store.deactivateAllStages();
}
this.isCustomStageForm = true;
},
hideAddStageForm() {
this.isCustomStageForm = false;
},
},
};
- page_title _('Cycle Analytics') - page_title _('Cycle Analytics')
- customizable_cycle_analytics = Feature.enabled?(:customizable_cycle_analytics)
- if cookies[:cycle_analytics_app] == 'true' - if cookies[:cycle_analytics_app] == 'true'
#js-cycle-analytics-app #js-cycle-analytics-app
...@@ -63,13 +64,17 @@ ...@@ -63,13 +64,17 @@
%nav.stage-nav %nav.stage-nav
%ul %ul
%stage-nav-item{ "v-for" => "stage in state.stages", ":key" => '`ca-stage-title-${stage.title}`', '@select' => 'selectStage(stage)', ":title" => "stage.title", ":is-user-allowed" => "stage.isUserAllowed", ":value" => "stage.value", ":is-active" => "stage.active" } %stage-nav-item{ "v-for" => "stage in state.stages", ":key" => '`ca-stage-title-${stage.title}`', '@select' => 'selectStage(stage)', ":title" => "stage.title", ":is-user-allowed" => "stage.isUserAllowed", ":value" => "stage.value", ":is-active" => "stage.active" }
- if customizable_cycle_analytics
%add-stage-button{ '@showform' => 'showAddStageForm', ":active" => 'isCustomStageForm' }
.section.stage-events .section.stage-events
%template{ "v-if" => "isLoadingStage" } %template{ "v-if" => "isLoadingStage" }
= icon("spinner spin") = icon("spinner spin")
%template{ "v-if" => "currentStage && !currentStage.isUserAllowed" } %template{ "v-if" => "currentStage && !currentStage.isUserAllowed" }
= render partial: "projects/cycle_analytics/no_access" = render partial: "projects/cycle_analytics/no_access"
%template{ "v-else" => true } %template{ "v-else" => true }
%template{ "v-if" => "isEmptyStage && !isLoadingStage" } %template{ "v-if" => "isEmptyStage && !isLoadingStage && !isCustomStageForm" }
= render partial: "projects/cycle_analytics/empty_stage" = render partial: "projects/cycle_analytics/empty_stage"
%template{ "v-if" => "state.events.length && !isLoadingStage && !isEmptyStage" } %template{ "v-if" => "state.events.length && !isLoadingStage && !isEmptyStage && !isCustomStageForm" }
%component{ ":is" => "currentStage.component", ":stage" => "currentStage", ":items" => "state.events" } %component{ ":is" => "currentStage.component", ":stage" => "currentStage", ":items" => "state.events" }
- if customizable_cycle_analytics
%custom-stage-form{ "v-if" => "isCustomStageForm" }
...@@ -41,4 +41,55 @@ describe 'Group Cycle Analytics', :js do ...@@ -41,4 +41,55 @@ describe 'Group Cycle Analytics', :js do
expect(page).to have_selector('.js-timeframe-filter', visible: true) expect(page).to have_selector('.js-timeframe-filter', visible: true)
end end
end end
describe 'Customizable cycle analytics', :js do
context 'enabled' do
before do
dropdown = page.find('.dropdown-groups')
dropdown.click
dropdown.find('a').click
end
context 'Add a stage button' do
it 'is visible' do
expect(page).to have_selector('.js-add-stage-button', visible: true)
expect(page).to have_text('Add a stage')
end
it 'becomes active when clicked ' do
btn = page.find('.js-add-stage-button')
expect(btn[:class]).not_to include('active')
btn.click
expect(btn[:class]).to include('active')
end
it 'displays the custom stage form when clicked' do
expect(page).not_to have_text('New stage')
page.find('.js-add-stage-button').click
expect(page).to have_text('New stage')
end
end
end
context 'not enabled' do
before do
stub_feature_flags(customizable_cycle_analytics: false)
dropdown = page.find('.dropdown-groups')
dropdown.click
dropdown.find('a').click
end
context 'Add a stage button' do
it 'is not visible' do
expect(page).to have_selector('.js-add-stage-button', visible: false)
end
end
end
end
end end
import { shallowMount } from '@vue/test-utils';
import AddStageButton from 'ee/analytics/cycle_analytics/components/add_stage_button.vue';
describe('AddStageButton', () => {
const active = false;
function createComponent(props) {
return shallowMount(AddStageButton, {
propsData: {
active,
...props,
},
});
}
let wrapper = null;
afterEach(() => {
wrapper.destroy();
});
describe('is not active', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('emits the `showform` event when clicked', () => {
wrapper = createComponent();
expect(wrapper.emitted().showform).toBeUndefined();
wrapper.trigger('click');
expect(wrapper.emitted().showform.length).toBe(1);
});
it('does not have the active class', () => {
expect(wrapper.classes('active')).toBe(false);
});
});
describe('is active', () => {
it('has the active class when active=true', () => {
wrapper = createComponent({ active: true });
expect(wrapper.classes('active')).toBe(true);
});
});
});
...@@ -4469,6 +4469,9 @@ msgstr "" ...@@ -4469,6 +4469,9 @@ msgstr ""
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates." msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
msgstr "" msgstr ""
msgid "CustomCycleAnalytics|Add a stage"
msgstr ""
msgid "Customize colors" msgid "Customize colors"
msgstr "" msgstr ""
...@@ -10067,6 +10070,9 @@ msgstr "" ...@@ -10067,6 +10070,9 @@ msgstr ""
msgid "New snippet" msgid "New snippet"
msgstr "" msgstr ""
msgid "New stage"
msgstr ""
msgid "New subgroup" msgid "New subgroup"
msgstr "" msgstr ""
......
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