{{ $options.i18n.CREATE_VALUE_STREAM }}
{{ $options.i18n.EDIT_VALUE_STREAM }}
{{ $options.i18n.CREATE_VALUE_STREAM }}
-
{
});
};
-export const updateValueStream = (
- { state, commit, dispatch, getters },
- { id: valueStreamId, ...data },
-) => {
+export const updateValueStream = ({ commit, getters }, { id: valueStreamId, ...data }) => {
const { namespacePath } = getters;
commit(types.REQUEST_UPDATE_VALUE_STREAM);
return apiUpdateValueStream({ namespacePath, valueStreamId, data })
.then(({ data: newValueStream }) => {
- if (!state.features.vsaStandaloneSettingsPage) {
- commit(types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS, newValueStream);
- return dispatch('fetchCycleAnalyticsData');
- }
-
return commit(types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS, newValueStream);
})
.catch(({ response } = {}) => {
diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/index.js b/ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/index.js
index bb97eecb56c612ee76caed58a280cc97a287af5d..c666407959e500b5d2874ebfa897294f69468d66 100644
--- a/ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/index.js
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/index.js
@@ -6,10 +6,7 @@ import VSASettingsApp from './components/app.vue';
export default () => {
const el = document.getElementById('js-vsa-settings-app');
-
- if (!el || !gon.features?.vsaStandaloneSettingsPage) {
- return false;
- }
+ if (!el) return false;
const { isEditPage, vsaPath } = el.dataset;
const initialData = buildCycleAnalyticsInitialData(el.dataset);
diff --git a/ee/app/controllers/ee/projects/analytics/cycle_analytics/value_streams_controller.rb b/ee/app/controllers/ee/projects/analytics/cycle_analytics/value_streams_controller.rb
index 4ecb36fe81dff573b6dd1f962f44ee3c77fd8532..30ca56b19784d15b996b2e4ab4f4a27c3e2ba121 100644
--- a/ee/app/controllers/ee/projects/analytics/cycle_analytics/value_streams_controller.rb
+++ b/ee/app/controllers/ee/projects/analytics/cycle_analytics/value_streams_controller.rb
@@ -11,10 +11,6 @@ module ValueStreamsController
prepended do
before_action :load_stage_events, only: %i[new edit]
before_action :value_stream, only: %i[edit]
-
- before_action do
- push_frontend_feature_flag(:vsa_standalone_settings_page, project.namespace)
- end
end
end
end
diff --git a/ee/app/controllers/ee/projects/cycle_analytics_controller.rb b/ee/app/controllers/ee/projects/cycle_analytics_controller.rb
index 46e7774a8adf20d9672e4321a773ee025ec77fd4..bc32cb7ed4626cb8467cf1c02f4de3c20975da8f 100644
--- a/ee/app/controllers/ee/projects/cycle_analytics_controller.rb
+++ b/ee/app/controllers/ee/projects/cycle_analytics_controller.rb
@@ -7,12 +7,6 @@ module CycleAnalyticsController
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
- prepended do
- before_action do
- push_frontend_feature_flag(:vsa_standalone_settings_page, project.namespace)
- end
- end
-
override :value_stream
def value_stream
return super unless params[:value_stream_id]
diff --git a/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb b/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb
index fe753e02754c9517ee0c093f1dc69e087c788f03..ede1712e351d7cc9945e0000ee047967885cb0b0 100644
--- a/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb
+++ b/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb
@@ -8,10 +8,6 @@ class Groups::Analytics::CycleAnalytics::ValueStreamsController < Groups::Analyt
before_action :load_stage_events, only: %i[new edit]
before_action :value_stream, only: %i[show edit update]
- before_action do
- push_frontend_feature_flag(:vsa_standalone_settings_page, namespace)
- end
-
layout 'group'
private
diff --git a/ee/app/controllers/groups/analytics/cycle_analytics_controller.rb b/ee/app/controllers/groups/analytics/cycle_analytics_controller.rb
index 88ef162aeae6c3908d87940254c1e0c3615c386a..0114e1f40f0d54b8002f18c09ef53f476c5f4a64 100644
--- a/ee/app/controllers/groups/analytics/cycle_analytics_controller.rb
+++ b/ee/app/controllers/groups/analytics/cycle_analytics_controller.rb
@@ -14,8 +14,6 @@ class Groups::Analytics::CycleAnalyticsController < Groups::Analytics::Applicati
before_action do
push_licensed_feature(:group_level_analytics_dashboard) if group_feature?(:group_level_analytics_dashboard)
- push_frontend_feature_flag(:vsa_standalone_settings_page, @group)
-
render_403 unless can?(current_user, :read_cycle_analytics, @group)
end
diff --git a/ee/config/feature_flags/beta/vsa_standalone_settings_page.yml b/ee/config/feature_flags/beta/vsa_standalone_settings_page.yml
deleted file mode 100644
index be66ade1006aff65d084e0c631b9c34396767496..0000000000000000000000000000000000000000
--- a/ee/config/feature_flags/beta/vsa_standalone_settings_page.yml
+++ /dev/null
@@ -1,9 +0,0 @@
----
-name: vsa_standalone_settings_page
-feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381002
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146538
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/444848
-milestone: '16.10'
-group: group::optimize
-type: beta
-default_enabled: false
diff --git a/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb b/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb
index dca207b1e78e93c5e56325cf3667c4b89c34a3c8..8deb142bd5fdfafeba19fba16cc7d17f90919ec9 100644
--- a/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb
+++ b/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb
@@ -339,29 +339,6 @@ def expect_successful_update(value_stream_name, expect_redirect)
it_behaves_like 'empty state'
end
end
-
- context 'when `vsa_standalone_settings_page` feature flag is disabled' do
- before do
- stub_feature_flags(vsa_standalone_settings_page: false)
- select_group(group, empty_state_selector)
- end
-
- it_behaves_like 'empty state'
-
- context '`New value stream...` button is selected' do
- before do
- find_by_testid('create-value-stream-button').click
- end
-
- it 'opens the create value stream form modal' do
- expect(page).to have_selector('[data-testid="value-stream-form-modal"]')
- end
-
- it 'does not navigate to the new value stream settings page' do
- expect(page).to have_current_path(group_analytics_cycle_analytics_path(group), ignore_query: true)
- end
- end
- end
end
context 'with a value stream' do
@@ -397,15 +374,6 @@ def expect_successful_update(value_stream_name, expect_redirect)
it_behaves_like 'create group value streams', true, true
it_behaves_like 'create sub group value streams', true, true
-
- context 'when `vsa_standalone_settings_page` feature flag is disabled' do
- before do
- stub_feature_flags(vsa_standalone_settings_page: false)
- end
-
- it_behaves_like 'create group value streams', true
- it_behaves_like 'create sub group value streams', true
- end
end
end
end
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_empty_state_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_empty_state_spec.js
index 8069ed756c3854bccd048d0be5e6acf42f102aad..5a8af651bda17a0b5fea9104bea2b67f1c61fc4d 100644
--- a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_empty_state_spec.js
+++ b/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_empty_state_spec.js
@@ -1,6 +1,5 @@
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ValueStreamEmptyState from 'ee/analytics/cycle_analytics/components/value_stream_empty_state.vue';
import {
EMPTY_STATE_ACTION_TEXT,
@@ -28,15 +27,9 @@ describe('ValueStreamEmptyState', () => {
},
provide: {
newValueStreamPath,
- glFeatures: {
- vsaStandaloneSettingsPage: true,
- },
...provide,
},
stubs: { GlEmptyState },
- directives: {
- GlModalDirective: createMockDirective('gl-modal-directive'),
- },
});
};
@@ -69,12 +62,6 @@ describe('ValueStreamEmptyState', () => {
expect(findPrimaryAction().attributes('href')).toBe(newValueStreamPath);
});
- it('does not bind modal directive to new value stream button', () => {
- const binding = getBinding(findPrimaryAction().element, 'gl-modal-directive');
-
- expect(binding.value).toBe(false);
- });
-
it('renders the learn more button', () => {
expect(findSecondaryAction().exists()).toBe(true);
expect(findSecondaryAction().text()).toBe(EMPTY_STATE_SECONDARY_TEXT);
@@ -141,20 +128,4 @@ describe('ValueStreamEmptyState', () => {
expect(findSecondaryAction().exists()).toBe(false);
});
});
-
- describe('vsaStandaloneSettingsPage = false', () => {
- beforeEach(() => {
- createComponent({ provide: { glFeatures: { vsaStandaloneSettingsPage: false } } });
- });
-
- it('renders new value stream button without a link', () => {
- expect(findPrimaryAction().attributes('href')).toBe(undefined);
- });
-
- it('binds modal directive to new value stream button', () => {
- const binding = getBinding(findPrimaryAction().element, 'gl-modal-directive');
-
- expect(binding.value).toBe('value-stream-form-modal');
- });
- });
});
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js
deleted file mode 100644
index 8aab5b53ed9d2a055df6762c9d5078753ba80f74..0000000000000000000000000000000000000000
--- a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js
+++ /dev/null
@@ -1,644 +0,0 @@
-import { GlAlert, GlModal, GlFormInput } from '@gitlab/ui';
-import Vue from 'vue';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
-import {
- i18n,
- PRESET_OPTIONS_BLANK,
- PRESET_OPTIONS_DEFAULT,
-} from 'ee/analytics/cycle_analytics/components/create_value_stream_form/constants';
-import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue';
-import CustomStageEventField from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_event_field.vue';
-import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue';
-import ValueStreamFormContent from 'ee/analytics/cycle_analytics/components/value_stream_form_content.vue';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import {
- convertObjectPropsToCamelCase,
- convertObjectPropsToSnakeCase,
-} from '~/lib/utils/common_utils';
-import {
- customStageEvents as formEvents,
- defaultStageConfig,
- rawCustomStage,
- groupLabels as defaultGroupLabels,
-} from '../mock_data';
-
-const scrollIntoViewMock = jest.fn();
-HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
-
-Vue.use(Vuex);
-
-describe('ValueStreamFormContent', () => {
- let wrapper = null;
- let trackingSpy = null;
-
- const createValueStreamMock = jest.fn(() => Promise.resolve());
- const updateValueStreamMock = jest.fn(() => Promise.resolve());
- const fetchGroupLabelsMock = jest.fn(() => Promise.resolve());
- const mockEvent = { preventDefault: jest.fn() };
- const mockToastShow = jest.fn();
- const streamName = 'Cool stream';
- const initialFormNameErrors = { name: ['Name field required'] };
- const initialFormStageErrors = {
- stages: [
- {
- name: ['Name field is required'],
- endEventIdentifier: ['Please select a start event first'],
- },
- ],
- };
- const formSubmissionErrors = {
- name: ['has already been taken'],
- stages: [
- {
- name: ['has already been taken'],
- },
- ],
- };
-
- const initialData = {
- stages: [convertObjectPropsToCamelCase(rawCustomStage)],
- id: 1337,
- name: 'Editable value stream',
- };
-
- const initialPreset = PRESET_OPTIONS_DEFAULT;
-
- const fakeStore = ({ state }) =>
- new Vuex.Store({
- state: {
- isCreatingValueStream: false,
- formEvents,
- defaultGroupLabels,
- ...state,
- },
- actions: {
- createValueStream: createValueStreamMock,
- updateValueStream: updateValueStreamMock,
- fetchGroupLabels: fetchGroupLabelsMock,
- },
- });
-
- const createComponent = ({ props = {}, data = {}, stubs = {}, state = {} } = {}) =>
- shallowMountExtended(ValueStreamFormContent, {
- store: fakeStore({ state }),
- data() {
- return {
- ...data,
- };
- },
- propsData: {
- defaultStageConfig,
- ...props,
- },
- mocks: {
- $toast: {
- show: mockToastShow,
- },
- },
- stubs: {
- ...stubs,
- },
- });
-
- const findModal = () => wrapper.findComponent(GlModal);
- const findExtendedFormFields = () => wrapper.findByTestId('extended-form-fields');
- const findDefaultStages = () => findExtendedFormFields().findAllComponents(DefaultStageFields);
- const findCustomStages = () => findExtendedFormFields().findAllComponents(CustomStageFields);
- const findPresetSelector = () => wrapper.findByTestId('vsa-preset-selector');
- const findRestoreButton = () => wrapper.findByTestId('vsa-reset-button');
- const findRestoreStageButton = (index) => wrapper.findByTestId(`stage-action-restore-${index}`);
- const findHiddenStages = () => wrapper.findAllByTestId('vsa-hidden-stage').wrappers;
- const findBtn = (btn) => findModal().props(btn);
- const findCustomStageEventField = (index = 0) =>
- wrapper.findAllComponents(CustomStageEventField).at(index);
- const findFieldErrors = (testId) => wrapper.findByTestId(testId).attributes('invalid-feedback');
- const findNameInput = () =>
- wrapper.findByTestId('create-value-stream-name').findComponent(GlFormInput);
- const findSubmitErrorAlert = () => wrapper.findComponent(GlAlert);
-
- const fillStageNameAtIndex = (name, index) =>
- findCustomStages().at(index).findComponent(GlFormInput).vm.$emit('input', name);
-
- const clickSubmit = () => findModal().vm.$emit('primary', mockEvent);
- const clickAddStage = () => findModal().vm.$emit('secondary', mockEvent);
- const clickRestoreStageAtIndex = (index) => findRestoreStageButton(index).vm.$emit('click');
- const expectFieldError = (testId, error = '') => expect(findFieldErrors(testId)).toBe(error);
- const expectCustomFieldError = (index, attr, error = '') =>
- expect(findCustomStageEventField(index).attributes(attr)).toBe(error);
- const expectStageTransitionKeys = (stages) =>
- stages.forEach((stage) => expect(stage.transitionKey).toContain('stage-'));
-
- describe('default state', () => {
- beforeEach(() => {
- wrapper = createComponent({ state: { defaultGroupLabels: null } });
- });
-
- it('has the extended fields', () => {
- expect(findExtendedFormFields().exists()).toBe(true);
- });
-
- it('sets the submit action text to "Create Value Stream"', () => {
- expect(findBtn('actionPrimary').text).toBe(i18n.FORM_TITLE);
- });
-
- it('renders the modal footer buttons', () => {
- expect(findModal().attributes('hide-footer')).toBeUndefined();
- });
-
- describe('Preset selector', () => {
- it('has the preset button', () => {
- expect(findPresetSelector().exists()).toBe(true);
- });
-
- it('will toggle between the blank and default templates', async () => {
- expect(findDefaultStages()).toHaveLength(defaultStageConfig.length);
- expect(findCustomStages()).toHaveLength(0);
-
- await findPresetSelector().vm.$emit('input', PRESET_OPTIONS_BLANK);
-
- expect(findDefaultStages()).toHaveLength(0);
- expect(findCustomStages()).toHaveLength(1);
-
- await findPresetSelector().vm.$emit('input', PRESET_OPTIONS_DEFAULT);
-
- expect(findDefaultStages()).toHaveLength(defaultStageConfig.length);
- expect(findCustomStages()).toHaveLength(0);
- });
-
- it('does not clear name when toggling templates', async () => {
- await findNameInput().vm.$emit('input', initialData.name);
-
- expect(findNameInput().attributes('value')).toBe(initialData.name);
-
- await findPresetSelector().vm.$emit('input', PRESET_OPTIONS_BLANK);
-
- expect(findNameInput().attributes('value')).toBe(initialData.name);
-
- await findPresetSelector().vm.$emit('input', PRESET_OPTIONS_DEFAULT);
-
- expect(findNameInput().attributes('value')).toBe(initialData.name);
- });
-
- it('each stage has a transition key when toggling', async () => {
- await findPresetSelector().vm.$emit('input', PRESET_OPTIONS_BLANK);
-
- expectStageTransitionKeys(wrapper.vm.stages);
-
- await findPresetSelector().vm.$emit('input', PRESET_OPTIONS_DEFAULT);
-
- expectStageTransitionKeys(wrapper.vm.stages);
- });
-
- it('does not display any hidden stages', () => {
- expect(findHiddenStages()).toHaveLength(0);
- });
-
- it('will fetch group labels', () => {
- expect(fetchGroupLabelsMock).toHaveBeenCalled();
- });
- });
-
- describe('Add stage button', () => {
- beforeEach(() => {
- wrapper = createComponent({
- stubs: {
- CustomStageFields,
- },
- });
- });
-
- it('has the add stage button', () => {
- expect(findBtn('actionSecondary')).toMatchObject({ text: 'Add another stage' });
- });
-
- it('adds a blank custom stage when clicked', async () => {
- expect(findDefaultStages()).toHaveLength(defaultStageConfig.length);
- expect(findCustomStages()).toHaveLength(0);
-
- await clickAddStage();
-
- expect(findDefaultStages()).toHaveLength(defaultStageConfig.length);
- expect(findCustomStages()).toHaveLength(1);
- });
-
- it('each stage has a transition key', () => {
- expectStageTransitionKeys(wrapper.vm.stages);
- });
- });
-
- describe('field validation', () => {
- beforeEach(() => {
- wrapper = createComponent({
- stubs: {
- CustomStageFields,
- },
- });
- });
-
- it('validates existing fields when clicked', async () => {
- const fieldTestId = 'create-value-stream-name';
- expect(findFieldErrors(fieldTestId)).toBeUndefined();
-
- await clickAddStage();
-
- expectFieldError(fieldTestId, 'Name is required');
- });
-
- it('does not allow duplicate stage names', async () => {
- const [firstDefaultStage] = defaultStageConfig;
- await findNameInput().vm.$emit('input', streamName);
-
- await clickAddStage();
- await fillStageNameAtIndex(firstDefaultStage.name, 0);
-
- // Trigger the field validation
- await clickAddStage();
-
- expectFieldError('custom-stage-name-3', 'Stage name already exists');
- });
- });
-
- describe('initial form stage errors', () => {
- const commonExtendedData = {
- props: {
- initialFormErrors: initialFormStageErrors,
- },
- };
-
- it('renders errors for a default stage field', () => {
- wrapper = createComponent({
- ...commonExtendedData,
- stubs: {
- DefaultStageFields,
- },
- });
-
- expectFieldError('default-stage-name-0', initialFormStageErrors.stages[0].name[0]);
- });
-
- it('renders errors for a custom stage field', () => {
- wrapper = createComponent({
- props: {
- ...commonExtendedData.props,
- initialPreset: PRESET_OPTIONS_BLANK,
- },
- stubs: {
- CustomStageFields,
- },
- });
-
- expectFieldError('custom-stage-name-0', initialFormStageErrors.stages[0].name[0]);
- expectCustomFieldError(
- 1,
- 'identifiererror',
- initialFormStageErrors.stages[0].endEventIdentifier[0],
- );
- });
- });
-
- describe('initial form name errors', () => {
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- initialFormErrors: initialFormNameErrors,
- },
- });
- });
-
- it('renders errors for the name field', () => {
- expectFieldError('create-value-stream-name', initialFormNameErrors.name[0]);
- });
- });
-
- describe('with valid fields', () => {
- beforeEach(() => {
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- describe('form submitted successfully', () => {
- beforeEach(async () => {
- wrapper = createComponent();
-
- await findNameInput().vm.$emit('input', streamName);
- clickSubmit();
- });
-
- it('calls the "createValueStream" event when submitted', () => {
- expect(createValueStreamMock).toHaveBeenCalledWith(expect.any(Object), {
- name: streamName,
- stages: [
- {
- custom: false,
- name: 'issue',
- },
- {
- custom: false,
- name: 'plan',
- },
- {
- custom: false,
- name: 'code',
- },
- ],
- });
- });
-
- it('clears the name field', () => {
- expect(findNameInput().attributes('value')).toBe('');
- });
-
- it('displays a toast message', () => {
- expect(mockToastShow).toHaveBeenCalledWith(`'${streamName}' Value Stream created`);
- });
-
- it('sends tracking information', () => {
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'submit_form', {
- label: 'create_value_stream',
- });
- });
- });
-
- describe('form submission fails', () => {
- beforeEach(async () => {
- wrapper = createComponent({
- props: {
- initialFormErrors: formSubmissionErrors,
- },
- stubs: {
- CustomStageFields,
- },
- });
-
- await findNameInput().vm.$emit('input', streamName);
- clickSubmit();
- });
-
- it('calls the createValueStream action', () => {
- expect(createValueStreamMock).toHaveBeenCalled();
- });
-
- it('does not clear the name field', () => {
- expect(findNameInput().attributes('value')).toBe(streamName);
- });
-
- it('does not display a toast message', () => {
- expect(mockToastShow).not.toHaveBeenCalled();
- });
-
- it('renders errors for the name field', () => {
- expectFieldError('create-value-stream-name', formSubmissionErrors.name[0]);
- });
-
- it('renders a dismissible generic alert error', async () => {
- expect(findSubmitErrorAlert().exists()).toBe(true);
- await findSubmitErrorAlert().vm.$emit('dismiss');
- expect(findSubmitErrorAlert().exists()).toBe(false);
- });
- });
- });
- });
-
- describe('isEditing=true', () => {
- const stageCount = initialData.stages.length;
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- initialPreset,
- initialData,
- isEditing: true,
- },
- });
- });
-
- it('does not have the preset button', () => {
- expect(findPresetSelector().exists()).toBe(false);
- });
-
- it('sets the submit action text to "Save value stream"', () => {
- expect(findBtn('actionPrimary').text).toBe(i18n.EDIT_FORM_ACTION);
- });
-
- it('does not display any hidden stages', () => {
- expect(findHiddenStages()).toHaveLength(0);
- });
-
- it('each stage has a transition key', () => {
- expectStageTransitionKeys(wrapper.vm.stages);
- });
-
- describe('restore defaults button', () => {
- it('restores the original name', async () => {
- const newName = 'name';
-
- await findNameInput().vm.$emit('input', newName);
-
- expect(findNameInput().attributes('value')).toBe(newName);
-
- await findRestoreButton().vm.$emit('click');
-
- expect(findNameInput().attributes('value')).toBe(initialData.name);
- });
-
- it('will clear the form fields', async () => {
- expect(findCustomStages()).toHaveLength(stageCount);
-
- await clickAddStage();
-
- expect(findCustomStages()).toHaveLength(stageCount + 1);
-
- await findRestoreButton().vm.$emit('click');
-
- expect(findCustomStages()).toHaveLength(stageCount);
- });
- });
-
- describe('with hidden stages', () => {
- const hiddenStages = defaultStageConfig.map((s) => ({ ...s, hidden: true }));
-
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- initialPreset,
- initialData: { ...initialData, stages: [...initialData.stages, ...hiddenStages] },
- isEditing: true,
- },
- });
- });
-
- it('displays hidden each stage', () => {
- expect(findHiddenStages()).toHaveLength(hiddenStages.length);
-
- findHiddenStages().forEach((s) => {
- expect(s.text()).toContain('Restore stage');
- });
- });
-
- it('when `Restore stage` is clicked, the stage is restored', async () => {
- expect(findHiddenStages()).toHaveLength(hiddenStages.length);
- expect(findDefaultStages()).toHaveLength(0);
- expect(findCustomStages()).toHaveLength(stageCount);
-
- await clickRestoreStageAtIndex(1);
-
- expect(findHiddenStages()).toHaveLength(hiddenStages.length - 1);
- expect(findDefaultStages()).toHaveLength(1);
- expect(findCustomStages()).toHaveLength(stageCount);
- });
-
- it('when a stage is restored it has a transition key', async () => {
- await clickRestoreStageAtIndex(1);
-
- expect(wrapper.vm.stages[stageCount].transitionKey).toContain(
- `stage-${hiddenStages[1].name}-`,
- );
- });
- });
-
- describe('Add stage button', () => {
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- initialPreset,
- initialData,
- isEditing: true,
- },
- stubs: {
- CustomStageFields,
- },
- });
- });
-
- it('has the add stage button', () => {
- expect(findBtn('actionSecondary')).toMatchObject({ text: i18n.BTN_ADD_ANOTHER_STAGE });
- });
-
- it('adds a blank custom stage when clicked', async () => {
- expect(findCustomStages()).toHaveLength(stageCount);
-
- await clickAddStage();
-
- expect(findCustomStages()).toHaveLength(stageCount + 1);
- });
-
- it('validates existing fields when clicked', async () => {
- const fieldTestId = 'create-value-stream-name';
- expect(findFieldErrors(fieldTestId)).toBeUndefined();
-
- await findNameInput().vm.$emit('input', '');
- await clickAddStage();
-
- expectFieldError(fieldTestId, 'Name is required');
- });
- });
-
- describe('with valid fields', () => {
- beforeEach(() => {
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- describe('form submitted successfully', () => {
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- initialPreset,
- initialData,
- isEditing: true,
- },
- });
-
- clickSubmit();
- });
-
- it('calls the "updateValueStreamMock" event when submitted', () => {
- expect(updateValueStreamMock).toHaveBeenCalledWith(expect.any(Object), {
- ...initialData,
- stages: initialData.stages.map((stage) =>
- convertObjectPropsToSnakeCase(stage, { deep: true }),
- ),
- });
- });
-
- it('displays a toast message', () => {
- expect(mockToastShow).toHaveBeenCalledWith(`'${initialData.name}' Value Stream saved`);
- });
-
- it('sends tracking information', () => {
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'submit_form', {
- label: 'edit_value_stream',
- });
- });
- });
-
- describe('form submission fails', () => {
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- initialFormErrors: formSubmissionErrors,
- initialData,
- initialPreset,
- isEditing: true,
- },
- stubs: {
- CustomStageFields,
- },
- });
-
- clickSubmit();
- });
-
- it('calls the updateValueStreamMock action', () => {
- expect(updateValueStreamMock).toHaveBeenCalled();
- });
-
- it('does not clear the name field', () => {
- const { name } = initialData;
-
- expect(findNameInput().attributes('value')).toBe(name);
- });
-
- it('does not display a toast message', () => {
- expect(mockToastShow).not.toHaveBeenCalled();
- });
-
- it('renders errors for the name field', () => {
- expectFieldError('create-value-stream-name', formSubmissionErrors.name[0]);
- });
-
- it('renders errors for a custom stage field', () => {
- expectFieldError('custom-stage-name-0', formSubmissionErrors.stages[0].name[0]);
- });
-
- it('renders a dismissible generic alert error', async () => {
- expect(findSubmitErrorAlert().exists()).toBe(true);
- await findSubmitErrorAlert().vm.$emit('dismiss');
- expect(findSubmitErrorAlert().exists()).toBe(false);
- });
- });
- });
- });
-
- describe('isFetchingGroupLabels=true', () => {
- beforeEach(() => {
- wrapper = createComponent({
- state: {
- defaultGroupLabels: [],
- isFetchingGroupLabels: true,
- },
- });
- });
-
- it('hides the modal footer buttons', () => {
- expect(findModal().attributes('hide-footer')).toBe('true');
- });
- });
-});
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_spec.js
deleted file mode 100644
index d333e4b7759e86c0c2313cfdeb2da0e908f7638d..0000000000000000000000000000000000000000
--- a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_spec.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import ValueStreamForm from 'ee/analytics/cycle_analytics/components/value_stream_form.vue';
-import ValueStreamFormContent from 'ee/analytics/cycle_analytics/components/value_stream_form_content.vue';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { rawCustomStage, valueStreams, defaultStageConfig } from '../mock_data';
-
-Vue.use(Vuex);
-
-const [selectedValueStream] = valueStreams;
-const camelCustomStage = convertObjectPropsToCamelCase(rawCustomStage);
-const stages = [camelCustomStage];
-const initialData = { name: '', stages: [] };
-
-const fakeStore = ({ state }) =>
- new Vuex.Store({
- state: {
- createValueStreamErrors: {},
- defaultStageConfig,
- ...state,
- },
- });
-
-const createComponent = ({ props = {}, state = {} } = {}) =>
- extendedWrapper(
- shallowMount(ValueStreamForm, {
- store: fakeStore({ state }),
- propsData: {
- defaultStageConfig,
- ...props,
- },
- }),
- );
-
-describe('ValueStreamForm', () => {
- let wrapper = null;
-
- const findForm = () => wrapper.findComponent(ValueStreamFormContent);
- const findFormProps = () => findForm().props();
-
- describe('default state', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
- it('renders the form component', () => {
- expect(findForm().exists()).toBe(true);
- expect(findFormProps()).toMatchObject({
- defaultStageConfig,
- initialData,
- isEditing: false,
- });
- });
-
- it('emits `hidden` when the modal is hidden', () => {
- expect(wrapper.emitted('hidden')).toBeUndefined();
-
- findForm().vm.$emit('hidden');
-
- expect(wrapper.emitted('hidden')).toHaveLength(1);
- });
- });
-
- describe('when editing', () => {
- beforeEach(() => {
- wrapper = createComponent({
- props: { isEditing: true },
- state: { selectedValueStream, stages },
- });
- });
-
- it('sets the form initialData', () => {
- const props = findFormProps();
- expect(props.initialData).toMatchObject({
- id: selectedValueStream.id,
- name: selectedValueStream.name,
- stages: [
- camelCustomStage,
- ...defaultStageConfig.map(({ custom, name }) => ({ custom, name, hidden: true })),
- ],
- });
- });
- });
-
- describe('with createValueStreamErrors', () => {
- const nameError = "Name can't be blank";
- beforeEach(() => {
- wrapper = createComponent({
- state: { createValueStreamErrors: { name: nameError } },
- });
- });
-
- it('sets the form initialFormErrors', () => {
- const props = findFormProps();
- expect(props.initialFormErrors).toEqual({ name: nameError });
- });
- });
-});
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_select_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_select_spec.js
index af56493feb885b97f1524e58fe854c42557c272d..f867e202063175b09e37ca955cdb84101b4cd8c3 100644
--- a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_select_spec.js
+++ b/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_select_spec.js
@@ -5,7 +5,7 @@ import Vuex from 'vuex';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { createMockDirective } from 'helpers/vue_mock_directive';
import {
valueStreams,
defaultStageConfig,
@@ -66,9 +66,6 @@ describe('ValueStreamSelect', () => {
provide: {
newValueStreamPath,
editValueStreamPath,
- glFeatures: {
- vsaStandaloneSettingsPage: true,
- },
...provide,
},
mocks: {
@@ -81,8 +78,7 @@ describe('ValueStreamSelect', () => {
},
});
- const findModal = (modal) => wrapper.findByTestId(`${modal}-value-stream-modal`);
- const submitModal = (modal) => findModal(modal).vm.$emit('primary', mockEvent);
+ const findDeleteModal = () => wrapper.findByTestId('delete-value-stream-modal');
const findSelectValueStreamDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
const findCreateValueStreamOption = () => wrapper.findByTestId('create-value-stream-option');
const findCreateValueStreamButton = () => wrapper.findByTestId('create-value-stream-button');
@@ -159,54 +155,6 @@ describe('ValueStreamSelect', () => {
expect(findEditValueStreamButton().text()).toBe('Edit');
expect(findEditValueStreamButton().attributes('href')).toBe(editValueStreamPathWithId);
});
-
- it('does not bind modal directive to edit button', () => {
- const binding = getBinding(findEditValueStreamButton().element, 'gl-modal-directive');
-
- expect(binding.value).toBe(false);
- });
-
- it('does not bind modal directive to create option', () => {
- const binding = getBinding(findCreateValueStreamOption().element, 'gl-modal-directive');
-
- expect(binding.value).toBe(false);
- });
-
- describe('vsaStandaloneSettingsPage = false', () => {
- beforeEach(() => {
- wrapper = createComponent({
- mountFn: mountExtended,
- initialState: {
- valueStreams,
- selectedValueStream: {
- ...selectedValueStream,
- isCustom: true,
- },
- },
- provide: { glFeatures: { vsaStandaloneSettingsPage: false } },
- });
- });
-
- it('renders create option without a link', () => {
- expect(findCreateValueStreamOption().attributes('href')).toBe(undefined);
- });
-
- it('binds modal directive to create option', () => {
- const binding = getBinding(findCreateValueStreamOption().element, 'gl-modal-directive');
-
- expect(binding.value).toBe('value-stream-form-modal');
- });
-
- it('renders edit button without a link', () => {
- expect(findEditValueStreamButton().attributes('href')).toBe(undefined);
- });
-
- it('binds modal directive to edit button', () => {
- const binding = getBinding(findEditValueStreamButton().element, 'gl-modal-directive');
-
- expect(binding.value).toBe('value-stream-form-modal');
- });
- });
});
describe('with canEdit=false', () => {
@@ -290,12 +238,6 @@ describe('ValueStreamSelect', () => {
expect(findCreateValueStreamButton().attributes('href')).toBe(newValueStreamPath);
});
- it('does not bind modal directive to create value stream button', () => {
- const binding = getBinding(findCreateValueStreamButton().element, 'gl-modal-directive');
-
- expect(binding.value).toBe(false);
- });
-
it('does not display the select value stream dropdown', () => {
expect(findSelectValueStreamDropdown().exists()).toBe(false);
});
@@ -303,27 +245,6 @@ describe('ValueStreamSelect', () => {
it('does not render an edit button for default value streams', () => {
expect(findEditValueStreamButton().exists()).toBe(false);
});
-
- describe('vsaStandaloneSettingsPage = false', () => {
- beforeEach(() => {
- wrapper = createComponent({
- initialState: {
- valueStreams: [],
- },
- provide: { glFeatures: { vsaStandaloneSettingsPage: false } },
- });
- });
-
- it('renders create value stream button without a link', () => {
- expect(findCreateValueStreamButton().attributes('href')).toBe(undefined);
- });
-
- it('binds modal directive to create value stream button', () => {
- const binding = getBinding(findCreateValueStreamButton().element, 'gl-modal-directive');
-
- expect(binding.value).toBe('value-stream-form-modal');
- });
- });
});
describe('Delete value stream modal', () => {
@@ -341,7 +262,7 @@ describe('ValueStreamSelect', () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- submitModal('delete');
+ findDeleteModal().vm.$emit('primary', mockEvent);
});
it('calls the "deleteValueStream" event when submitted', () => {
diff --git a/ee/spec/frontend/analytics/cycle_analytics/store/actions/value_streams_spec.js b/ee/spec/frontend/analytics/cycle_analytics/store/actions/value_streams_spec.js
index 174e5b489f4794608671430e640cc362c1a94188..549f083ab5a700628e2e0c40a30161fac4c40acc 100644
--- a/ee/spec/frontend/analytics/cycle_analytics/store/actions/value_streams_spec.js
+++ b/ee/spec/frontend/analytics/cycle_analytics/store/actions/value_streams_spec.js
@@ -160,7 +160,7 @@ describe('Value Stream Analytics actions / value streams', () => {
describe('with no errors', () => {
beforeEach(() => {
- state = { currentGroup, features: { vsaStandaloneSettingsPage: true } };
+ state = { currentGroup };
mock.onPut(endpoints.valueStreamData).replyOnce(HTTP_STATUS_OK, updateResp);
});
@@ -170,25 +170,6 @@ describe('Value Stream Analytics actions / value streams', () => {
{ type: types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS, payload: updateResp },
]);
});
-
- describe('when `vsaStandaloneSettingsPage` feature flag is disabled', () => {
- beforeEach(() => {
- state = { currentGroup, features: { vsaStandaloneSettingsPage: false } };
- });
-
- it(`commits the ${types.REQUEST_UPDATE_VALUE_STREAM} and ${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} mutations and dispatches the 'fetchCycleAnalyticsData' action`, () => {
- return testAction(
- actions.updateValueStream,
- payload,
- state,
- [
- { type: types.REQUEST_UPDATE_VALUE_STREAM },
- { type: types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS, payload: updateResp },
- ],
- [{ type: 'fetchCycleAnalyticsData' }],
- );
- });
- });
});
describe('with errors', () => {
diff --git a/qa/qa/ee/page/value_stream_analytics.rb b/qa/qa/ee/page/value_stream_analytics.rb
index 9102e6cb9cc46fd4b94abeb5d5469277d0c36fc7..36988977b3169f0b040bdc8c51f6875a789c4657 100644
--- a/qa/qa/ee/page/value_stream_analytics.rb
+++ b/qa/qa/ee/page/value_stream_analytics.rb
@@ -14,10 +14,12 @@ def self.included(base)
element 'create-value-stream-button'
end
- view "ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_form_content.vue" do
+ # rubocop:disable Layout/LineLength -- Long filename
+ view "ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content.vue" do
element 'create-value-stream-name-input'
element 'vsa-preset-selector'
end
+ # rubocop:enable Layout/LineLength
view "ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue" do
element 'vsa-path-navigation'
@@ -156,12 +158,7 @@ def create_value_stream
# @return [void]
def add_another_stage
# footer buttons are generic UI components from gitlab/ui
- # Remove else once vsa_standalone_settings_page feature flag is removed
- if has_button?("Add a stage")
- find_button("Add a stage").click
- else
- find_button("Add another stage").click
- end
+ find_button("Add a stage").click
end
# Select custom event in stage