Commit 8216a617 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Clement Ho

Added add stage button

Added new button to launch the new
stage form for customisable cycle
analytics.

Move add stage button to vue component

Added basic test for add stage button

Move css to bootstrap classes

Removes scss styling that can be applied
via bootstrap classes and adds the relevant
classes to html elements

Added feature tests for add stage button

Moved add stage functionality to ee mixin

Created addStageMixin for the add stage
functionality needed in EE. Added a stub
mixin to the CE dir.
parent 1eb24b98
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