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>
import { GlEmptyState, GlLoadingIcon, GlButton } from '@gitlab/ui';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { mapActions, mapState, mapGetters } from 'vuex';
import { sprintf, __ } from '~/locale';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import { PROJECTS_PER_PAGE, STAGE_ACTIONS } from '../constants';
import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter.vue';
......@@ -20,13 +21,13 @@ import CustomStageForm from './custom_stage_form.vue';
import PathNavigation from './path_navigation.vue';
import MetricCard from '../../shared/components/metric_card.vue';
import FilterBar from './filter_bar.vue';
import ValueStreamSelect from './value_stream_select.vue';
export default {
name: 'CycleAnalytics',
components: {
DateRange,
DurationChart,
GlButton,
GlLoadingIcon,
GlEmptyState,
GroupsDropdownFilter,
......@@ -40,6 +41,7 @@ export default {
PathNavigation,
MetricCard,
FilterBar,
ValueStreamSelect,
},
mixins: [UrlSyncMixin],
props: {
......@@ -198,8 +200,9 @@ export default {
onStageReorder(data) {
this.reorderStage(data);
},
onCreateValueStream() {
// stub handler - to be implemented in a follow up
onCreateValueStream({ name }) {
// stub - this will eventually trigger a vuex action
this.$toast.show(sprintf(__("'%{name}' Value Stream created"), { name }));
},
},
multiProjectSelect: true,
......@@ -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"
>
<h3>{{ __('Value Stream Analytics') }}</h3>
<div
<value-stream-select
v-if="shouldDisplayCreateMultipleValueStreams"
class="gl-align-self-center"
:class="{
'gl-w-full': isXSBreakpoint,
'gl-mt-5': !isXSBreakpoint,
}"
>
<gl-button data-testid="create-value-stream" @click="onCreateValueStream">{{
__('Create new value stream')
}}</gl-button>
</div>
@create="onCreateValueStream"
/>
</div>
<div class="mw-100">
<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';
import createStore from './store';
import { buildCycleAnalyticsInitialData } from '../shared/utils';
import { parseBoolean } from '~/lib/utils/common_utils';
import { GlToast } from '@gitlab/ui';
Vue.use(GlToast);
export default () => {
const el = document.querySelector('#js-cycle-analytics-app');
......
......@@ -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 Daterange from 'ee/analytics/shared/components/daterange.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 httpStatusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils';
......@@ -44,6 +45,7 @@ const defaultStubs = {
'labels-selector': true,
DurationChart: true,
GroupsDropdownFilter: true,
ValueStreamSelect: true,
};
const defaultFeatureFlags = {
......@@ -64,6 +66,12 @@ const initialCycleAnalyticsState = {
group: selectedGroup,
};
const mocks = {
$toast: {
show: jest.fn(),
},
};
function createComponent({
opts = {
stubs: defaultStubs,
......@@ -86,6 +94,7 @@ function createComponent({
hideGroupDropDown,
...props,
},
mocks,
...opts,
});
......@@ -171,7 +180,7 @@ describe('Cycle Analytics component', () => {
};
const displaysCreateValueStream = flag => {
expect(wrapper.find('[data-testid="create-value-stream"]').exists()).toBe(flag);
expect(wrapper.find(ValueStreamSelect).exists()).toBe(flag);
};
beforeEach(() => {
......@@ -237,6 +246,7 @@ describe('Cycle Analytics component', () => {
it('does not display the path navigation', () => {
displaysPathNavigation(false);
});
it('does not display the create multiple value streams button', () => {
displaysCreateValueStream(false);
});
......@@ -269,6 +279,14 @@ describe('Cycle Analytics component', () => {
it('displays the create multiple value streams button', () => {
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 ""
msgid "'%{level}' is not a valid visibility level"
msgstr ""
msgid "'%{name}' Value Stream created"
msgstr ""
msgid "'%{name}' stage already exists"
msgstr ""
......@@ -6854,6 +6857,9 @@ msgstr ""
msgid "Create snippet"
msgstr ""
msgid "Create value stream"
msgstr ""
msgid "Create wildcard: %{searchTerm}"
msgstr ""
......@@ -9448,6 +9454,9 @@ msgstr ""
msgid "Example: @sub\\.company\\.com$"
msgstr ""
msgid "Example: My value stream"
msgstr ""
msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
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."
msgstr ""
msgid "Value Stream Name"
msgstr ""
msgid "ValueStreamAnalytics|%{days}d"
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