Commit 00be0657 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '221202-fe-multiple-value-streams-create-new-value-stream-mvc' into 'master'

Create new value stream modal form

See merge request gitlab-org/gitlab!35731
parents 71a09d24 1944c395
<script> <script>
import { GlEmptyState, GlLoadingIcon, GlButton } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { sprintf, __ } from '~/locale';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import { PROJECTS_PER_PAGE, STAGE_ACTIONS } from '../constants'; import { PROJECTS_PER_PAGE, STAGE_ACTIONS } from '../constants';
import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter.vue'; import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter.vue';
...@@ -20,13 +21,13 @@ import CustomStageForm from './custom_stage_form.vue'; ...@@ -20,13 +21,13 @@ import CustomStageForm from './custom_stage_form.vue';
import PathNavigation from './path_navigation.vue'; import PathNavigation from './path_navigation.vue';
import MetricCard from '../../shared/components/metric_card.vue'; import MetricCard from '../../shared/components/metric_card.vue';
import FilterBar from './filter_bar.vue'; import FilterBar from './filter_bar.vue';
import ValueStreamSelect from './value_stream_select.vue';
export default { export default {
name: 'CycleAnalytics', name: 'CycleAnalytics',
components: { components: {
DateRange, DateRange,
DurationChart, DurationChart,
GlButton,
GlLoadingIcon, GlLoadingIcon,
GlEmptyState, GlEmptyState,
GroupsDropdownFilter, GroupsDropdownFilter,
...@@ -40,6 +41,7 @@ export default { ...@@ -40,6 +41,7 @@ export default {
PathNavigation, PathNavigation,
MetricCard, MetricCard,
FilterBar, FilterBar,
ValueStreamSelect,
}, },
mixins: [UrlSyncMixin], mixins: [UrlSyncMixin],
props: { props: {
...@@ -198,8 +200,9 @@ export default { ...@@ -198,8 +200,9 @@ export default {
onStageReorder(data) { onStageReorder(data) {
this.reorderStage(data); this.reorderStage(data);
}, },
onCreateValueStream() { onCreateValueStream({ name }) {
// stub handler - to be implemented in a follow up // stub - this will eventually trigger a vuex action
this.$toast.show(sprintf(__("'%{name}' Value Stream created"), { name }));
}, },
}, },
multiProjectSelect: true, multiProjectSelect: true,
...@@ -223,18 +226,15 @@ export default { ...@@ -223,18 +226,15 @@ export default {
class="gl-mb-3 gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row gl-justify-content-space-between" class="gl-mb-3 gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row gl-justify-content-space-between"
> >
<h3>{{ __('Value Stream Analytics') }}</h3> <h3>{{ __('Value Stream Analytics') }}</h3>
<div <value-stream-select
v-if="shouldDisplayCreateMultipleValueStreams" v-if="shouldDisplayCreateMultipleValueStreams"
class="gl-align-self-center" class="gl-align-self-center"
:class="{ :class="{
'gl-w-full': isXSBreakpoint, 'gl-w-full': isXSBreakpoint,
'gl-mt-5': !isXSBreakpoint, 'gl-mt-5': !isXSBreakpoint,
}" }"
> @create="onCreateValueStream"
<gl-button data-testid="create-value-stream" @click="onCreateValueStream">{{ />
__('Create new value stream')
}}</gl-button>
</div>
</div> </div>
<div class="mw-100"> <div class="mw-100">
<div class="mt-3 py-2 px-3 bg-gray-light border-top border-bottom"> <div class="mt-3 py-2 px-3 bg-gray-light border-top border-bottom">
......
<script>
import { GlButton, GlForm, GlFormInput, GlModal, GlModalDirective } from '@gitlab/ui';
export default {
components: {
GlButton,
GlForm,
GlFormInput,
GlModal,
},
directives: {
GlModalDirective,
},
data() {
return {
name: '',
};
},
computed: {
isValid() {
return Boolean(this.name.length);
},
},
};
</script>
<template>
<gl-form>
<gl-button v-gl-modal-directive="'create-value-stream-modal'">{{
__('Create new value stream')
}}</gl-button>
<gl-modal
modal-id="create-value-stream-modal"
:title="__('Value Stream Name')"
:action-primary="{
text: __('Create value stream'),
attributes: [
{ variant: 'primary' },
{
disabled: !isValid,
},
],
}"
:action-cancel="{ text: __('Cancel') }"
@primary="$emit('create', { name })"
>
<gl-form-input id="name" v-model="name" :placeholder="__('Example: My value stream')" />
</gl-modal>
</gl-form>
</template>
...@@ -3,6 +3,9 @@ import CycleAnalytics from './components/base.vue'; ...@@ -3,6 +3,9 @@ import CycleAnalytics from './components/base.vue';
import createStore from './store'; import createStore from './store';
import { buildCycleAnalyticsInitialData } from '../shared/utils'; import { buildCycleAnalyticsInitialData } from '../shared/utils';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { GlToast } from '@gitlab/ui';
Vue.use(GlToast);
export default () => { export default () => {
const el = document.querySelector('#js-cycle-analytics-app'); const el = document.querySelector('#js-cycle-analytics-app');
......
...@@ -18,6 +18,7 @@ import FilterBar from 'ee/analytics/cycle_analytics/components/filter_bar.vue'; ...@@ -18,6 +18,7 @@ import FilterBar from 'ee/analytics/cycle_analytics/components/filter_bar.vue';
import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue'; import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue';
import Daterange from 'ee/analytics/shared/components/daterange.vue'; import Daterange from 'ee/analytics/shared/components/daterange.vue';
import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue'; import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
...@@ -44,6 +45,7 @@ const defaultStubs = { ...@@ -44,6 +45,7 @@ const defaultStubs = {
'labels-selector': true, 'labels-selector': true,
DurationChart: true, DurationChart: true,
GroupsDropdownFilter: true, GroupsDropdownFilter: true,
ValueStreamSelect: true,
}; };
const defaultFeatureFlags = { const defaultFeatureFlags = {
...@@ -64,6 +66,12 @@ const initialCycleAnalyticsState = { ...@@ -64,6 +66,12 @@ const initialCycleAnalyticsState = {
group: selectedGroup, group: selectedGroup,
}; };
const mocks = {
$toast: {
show: jest.fn(),
},
};
function createComponent({ function createComponent({
opts = { opts = {
stubs: defaultStubs, stubs: defaultStubs,
...@@ -86,6 +94,7 @@ function createComponent({ ...@@ -86,6 +94,7 @@ function createComponent({
hideGroupDropDown, hideGroupDropDown,
...props, ...props,
}, },
mocks,
...opts, ...opts,
}); });
...@@ -171,7 +180,7 @@ describe('Cycle Analytics component', () => { ...@@ -171,7 +180,7 @@ describe('Cycle Analytics component', () => {
}; };
const displaysCreateValueStream = flag => { const displaysCreateValueStream = flag => {
expect(wrapper.find('[data-testid="create-value-stream"]').exists()).toBe(flag); expect(wrapper.find(ValueStreamSelect).exists()).toBe(flag);
}; };
beforeEach(() => { beforeEach(() => {
...@@ -237,6 +246,7 @@ describe('Cycle Analytics component', () => { ...@@ -237,6 +246,7 @@ describe('Cycle Analytics component', () => {
it('does not display the path navigation', () => { it('does not display the path navigation', () => {
displaysPathNavigation(false); displaysPathNavigation(false);
}); });
it('does not display the create multiple value streams button', () => { it('does not display the create multiple value streams button', () => {
displaysCreateValueStream(false); displaysCreateValueStream(false);
}); });
...@@ -269,6 +279,14 @@ describe('Cycle Analytics component', () => { ...@@ -269,6 +279,14 @@ describe('Cycle Analytics component', () => {
it('displays the create multiple value streams button', () => { it('displays the create multiple value streams button', () => {
displaysCreateValueStream(true); displaysCreateValueStream(true);
}); });
it('displays a toast message when value stream is created', () => {
wrapper.find(ValueStreamSelect).vm.$emit('create', { name: 'cool new stream' });
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(
"'cool new stream' Value Stream created",
);
});
}); });
}); });
......
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
describe('ValueStreamSelect', () => {
let wrapper = null;
const createComponent = () => shallowMount(ValueStreamSelect, {});
const findModal = () => wrapper.find(GlModal);
const submitButtonDisabledState = () => findModal().props('actionPrimary').attributes[1].disabled;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('Create value stream form', () => {
it('submit button is disabled', () => {
expect(submitButtonDisabledState()).toBe(true);
});
describe('with valid fields', () => {
beforeEach(async () => {
wrapper = createComponent();
await wrapper.setData({ name: 'Cool stream' });
});
it('submit button is enabled', () => {
expect(submitButtonDisabledState()).toBe(false);
});
it('emits the "create" event when submitted', () => {
findModal().vm.$emit('primary');
expect(wrapper.emitted().create[0]).toEqual([{ name: 'Cool stream' }]);
});
});
});
});
...@@ -735,6 +735,9 @@ msgstr "" ...@@ -735,6 +735,9 @@ msgstr ""
msgid "'%{level}' is not a valid visibility level" msgid "'%{level}' is not a valid visibility level"
msgstr "" msgstr ""
msgid "'%{name}' Value Stream created"
msgstr ""
msgid "'%{name}' stage already exists" msgid "'%{name}' stage already exists"
msgstr "" msgstr ""
...@@ -6854,6 +6857,9 @@ msgstr "" ...@@ -6854,6 +6857,9 @@ msgstr ""
msgid "Create snippet" msgid "Create snippet"
msgstr "" msgstr ""
msgid "Create value stream"
msgstr ""
msgid "Create wildcard: %{searchTerm}" msgid "Create wildcard: %{searchTerm}"
msgstr "" msgstr ""
...@@ -9448,6 +9454,9 @@ msgstr "" ...@@ -9448,6 +9454,9 @@ msgstr ""
msgid "Example: @sub\\.company\\.com$" msgid "Example: @sub\\.company\\.com$"
msgstr "" msgstr ""
msgid "Example: My value stream"
msgstr ""
msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula." msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
msgstr "" msgstr ""
...@@ -25589,6 +25598,9 @@ msgstr "" ...@@ -25589,6 +25598,9 @@ msgstr ""
msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project." msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr "" msgstr ""
msgid "Value Stream Name"
msgstr ""
msgid "ValueStreamAnalytics|%{days}d" msgid "ValueStreamAnalytics|%{days}d"
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