Commit f6a578d8 authored by Andrew Fontaine's avatar Andrew Fontaine

Integrate Edit Feature Flag Page with New UI

By passing a version in to the form, the form can now handle new version
flags to edit. The update action has also been beefed up.
parent d02b2af8
...@@ -36,10 +36,12 @@ export default { ...@@ -36,10 +36,12 @@ export default {
'name', 'name',
'description', 'description',
'scopes', 'scopes',
'strategies',
'isLoading', 'isLoading',
'hasError', 'hasError',
'iid', 'iid',
'active', 'active',
'version',
]), ]),
title() { title() {
return this.iid return this.iid
...@@ -80,10 +82,12 @@ export default { ...@@ -80,10 +82,12 @@ export default {
:name="name" :name="name"
:description="description" :description="description"
:scopes="scopes" :scopes="scopes"
:strategies="strategies"
:cancel-path="path" :cancel-path="path"
:submit-text="__('Save changes')" :submit-text="__('Save changes')"
:environments-endpoint="environmentsEndpoint" :environments-endpoint="environmentsEndpoint"
:active="active" :active="active"
:version="version"
@handleSubmit="data => updateFeatureFlag(data)" @handleSubmit="data => updateFeatureFlag(data)"
/> />
</template> </template>
......
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import { memoize, isString, cloneDeep } from 'lodash'; import { memoize, isString, cloneDeep, isNumber } from 'lodash';
import { import {
GlButton, GlButton,
GlBadge, GlBadge,
...@@ -147,7 +147,7 @@ export default { ...@@ -147,7 +147,7 @@ export default {
}, },
deleteStrategy(s) { deleteStrategy(s) {
if (s.id) { if (isNumber(s.id)) {
Vue.set(s, 'shouldBeDestroyed', true); Vue.set(s, 'shouldBeDestroyed', true);
} else { } else {
this.formStrategies = this.formStrategies.filter(strategy => strategy !== s); this.formStrategies = this.formStrategies.filter(strategy => strategy !== s);
......
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import { isString } from 'lodash'; import { isNumber } from 'lodash';
import { import {
GlFormSelect, GlFormSelect,
GlFormInput, GlFormInput,
...@@ -122,18 +122,18 @@ export default { ...@@ -122,18 +122,18 @@ export default {
return ( return (
this.filteredEnvironments.length === 0 || this.filteredEnvironments.length === 0 ||
(this.filteredEnvironments.length === 1 && (this.filteredEnvironments.length === 1 &&
this.filteredEnvironments[0].environment_scope === '*') this.filteredEnvironments[0].environmentScope === '*')
); );
}, },
filteredEnvironments() { filteredEnvironments() {
return this.environments.filter(e => !e.shouldDestroy); return this.environments.filter(e => !e.shouldBeDestroyed);
}, },
}, },
methods: { methods: {
addEnvironment(environment) { addEnvironment(environment) {
const allEnvironmentsScope = this.environments.find(scope => scope.environmentScope === '*'); const allEnvironmentsScope = this.environments.find(scope => scope.environmentScope === '*');
if (allEnvironmentsScope) { if (allEnvironmentsScope) {
allEnvironmentsScope.shouldDestroy = true; allEnvironmentsScope.shouldBeDestroyed = true;
} }
this.environments.push({ environmentScope: environment }); this.environments.push({ environmentScope: environment });
this.onStrategyChange(); this.onStrategyChange();
...@@ -158,7 +158,7 @@ export default { ...@@ -158,7 +158,7 @@ export default {
}); });
}, },
removeScope(environment) { removeScope(environment) {
if (isString(environment.id)) { if (isNumber(environment.id)) {
Vue.set(environment, 'shouldBeDestroyed', true); Vue.set(environment, 'shouldBeDestroyed', true);
} else { } else {
this.environments = this.environments.filter(e => e !== environment); this.environments = this.environments.filter(e => e !== environment);
......
...@@ -3,7 +3,8 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -3,7 +3,8 @@ import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { mapFromScopesViewModel } from '../helpers'; import { NEW_VERSION_FLAG } from '../../../constants';
import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers';
/** /**
* Commits mutation to set the main endpoint * Commits mutation to set the main endpoint
...@@ -32,7 +33,12 @@ export const updateFeatureFlag = ({ state, dispatch }, params) => { ...@@ -32,7 +33,12 @@ export const updateFeatureFlag = ({ state, dispatch }, params) => {
dispatch('requestUpdateFeatureFlag'); dispatch('requestUpdateFeatureFlag');
axios axios
.put(state.endpoint, mapFromScopesViewModel(params)) .put(
state.endpoint,
params.version === NEW_VERSION_FLAG
? mapStrategiesToRails(params)
: mapFromScopesViewModel(params),
)
.then(() => { .then(() => {
dispatch('receiveUpdateFeatureFlagSuccess'); dispatch('receiveUpdateFeatureFlagSuccess');
visitUrl(state.path); visitUrl(state.path);
......
import * as types from './mutation_types'; import * as types from './mutation_types';
import { mapToScopesViewModel } from '../helpers'; import { mapToScopesViewModel, mapStrategiesToViewModel } from '../helpers';
import { LEGACY_FLAG } from '../../../constants';
export default { export default {
[types.SET_ENDPOINT](state, endpoint) { [types.SET_ENDPOINT](state, endpoint) {
...@@ -20,8 +21,8 @@ export default { ...@@ -20,8 +21,8 @@ export default {
state.iid = response.iid; state.iid = response.iid;
state.active = response.active; state.active = response.active;
state.scopes = mapToScopesViewModel(response.scopes); state.scopes = mapToScopesViewModel(response.scopes);
state.strategies = response.strategies || []; state.strategies = mapStrategiesToViewModel(response.strategies);
state.version = response.version || 1; state.version = response.version || LEGACY_FLAG;
}, },
[types.RECEIVE_FEATURE_FLAG_ERROR](state) { [types.RECEIVE_FEATURE_FLAG_ERROR](state) {
state.isLoading = false; state.isLoading = false;
......
import { LEGACY_FLAG } from '../../../constants';
export default () => ({ export default () => ({
endpoint: null, endpoint: null,
path: null, path: null,
...@@ -12,5 +14,5 @@ export default () => ({ ...@@ -12,5 +14,5 @@ export default () => ({
iid: null, iid: null,
active: true, active: true,
strategies: [], strategies: [],
version: 1, version: LEGACY_FLAG,
}); });
...@@ -181,7 +181,7 @@ export const mapStrategiesToRails = params => ({ ...@@ -181,7 +181,7 @@ export const mapStrategiesToRails = params => ({
name: s.name, name: s.name,
parameters: s.parameters, parameters: s.parameters,
_destroy: s.shouldBeDestroyed, _destroy: s.shouldBeDestroyed,
scopes: mapStrategyScopesToRails(s.scopes || []), scopes_attributes: mapStrategyScopesToRails(s.scopes || []),
})), })),
}, },
}); });
...@@ -2,6 +2,7 @@ import Vuex from 'vuex'; ...@@ -2,6 +2,7 @@ import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { GlToggle } from '@gitlab/ui'; import { GlToggle } from '@gitlab/ui';
import { LEGACY_FLAG, NEW_VERSION_FLAG } from 'ee/feature_flags/constants';
import Form from 'ee/feature_flags/components/form.vue'; import Form from 'ee/feature_flags/components/form.vue';
import editModule from 'ee/feature_flags/store/modules/edit'; import editModule from 'ee/feature_flags/store/modules/edit';
import EditFeatureFlag from 'ee/feature_flags/components/edit_feature_flag.vue'; import EditFeatureFlag from 'ee/feature_flags/components/edit_feature_flag.vue';
...@@ -22,21 +23,30 @@ describe('Edit feature flag form', () => { ...@@ -22,21 +23,30 @@ describe('Edit feature flag form', () => {
}); });
const factory = () => { const factory = () => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
wrapper = shallowMount(EditFeatureFlag, { wrapper = shallowMount(EditFeatureFlag, {
localVue, localVue,
propsData: { propsData: {
endpoint: `${TEST_HOST}/feature_flags.json'`, endpoint: `${TEST_HOST}/feature_flags.json`,
path: '/feature_flags', path: '/feature_flags',
environmentsEndpoint: 'environments.json', environmentsEndpoint: 'environments.json',
}, },
store, store,
provide: {
glFeatures: {
featureFlagsNewVersion: true,
},
},
}); });
}; };
beforeEach(done => { beforeEach(done => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(`${TEST_HOST}/feature_flags.json'`).replyOnce(200, { mock.onGet(`${TEST_HOST}/feature_flags.json`).replyOnce(200, {
id: 21, id: 21,
iid: 5, iid: 5,
active: true, active: true,
...@@ -44,6 +54,7 @@ describe('Edit feature flag form', () => { ...@@ -44,6 +54,7 @@ describe('Edit feature flag form', () => {
updated_at: '2019-01-17T17:27:39.778Z', updated_at: '2019-01-17T17:27:39.778Z',
name: 'feature_flag', name: 'feature_flag',
description: '', description: '',
version: LEGACY_FLAG,
edit_path: '/h5bp/html5-boilerplate/-/feature_flags/21/edit', edit_path: '/h5bp/html5-boilerplate/-/feature_flags/21/edit',
destroy_path: '/h5bp/html5-boilerplate/-/feature_flags/21', destroy_path: '/h5bp/html5-boilerplate/-/feature_flags/21',
scopes: [ scopes: [
...@@ -98,5 +109,31 @@ describe('Edit feature flag form', () => { ...@@ -98,5 +109,31 @@ describe('Edit feature flag form', () => {
it('should render feature flag form', () => { it('should render feature flag form', () => {
expect(wrapper.find(Form).exists()).toEqual(true); expect(wrapper.find(Form).exists()).toEqual(true);
}); });
it('should set the version of the form from the feature flag', () => {
expect(wrapper.find(Form).props('version')).toBe(LEGACY_FLAG);
mock.resetHandlers();
mock.onGet(`${TEST_HOST}/feature_flags.json`).replyOnce(200, {
id: 21,
iid: 5,
active: true,
created_at: '2019-01-17T17:27:39.778Z',
updated_at: '2019-01-17T17:27:39.778Z',
name: 'feature_flag',
description: '',
version: NEW_VERSION_FLAG,
edit_path: '/h5bp/html5-boilerplate/-/feature_flags/21/edit',
destroy_path: '/h5bp/html5-boilerplate/-/feature_flags/21',
strategies: [],
});
factory();
return axios.waitForAll().then(() => {
expect(wrapper.find(Form).props('version')).toBe(NEW_VERSION_FLAG);
});
});
}); });
}); });
...@@ -142,7 +142,7 @@ describe('Feature flags strategy', () => { ...@@ -142,7 +142,7 @@ describe('Feature flags strategy', () => {
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT, name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID }, parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
scopes: [ scopes: [
{ environmentScope: '*', shouldDestroy: true }, { environmentScope: '*', shouldBeDestroyed: true },
{ environmentScope: 'production' }, { environmentScope: 'production' },
], ],
}, },
......
...@@ -13,6 +13,15 @@ import { ...@@ -13,6 +13,15 @@ import {
toggleActive, toggleActive,
} from 'ee/feature_flags/store/modules/edit/actions'; } from 'ee/feature_flags/store/modules/edit/actions';
import state from 'ee/feature_flags/store/modules/edit/state'; import state from 'ee/feature_flags/store/modules/edit/state';
import {
mapStrategiesToRails,
mapFromScopesViewModel,
} from 'ee/feature_flags/store/modules/helpers';
import {
NEW_VERSION_FLAG,
LEGACY_FLAG,
ROLLOUT_STRATEGY_ALL_USERS,
} from 'ee/feature_flags/constants';
import * as types from 'ee/feature_flags/store/modules/edit/mutation_types'; import * as types from 'ee/feature_flags/store/modules/edit/mutation_types';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
...@@ -67,15 +76,62 @@ describe('Feature flags Edit Module actions', () => { ...@@ -67,15 +76,62 @@ describe('Feature flags Edit Module actions', () => {
describe('success', () => { describe('success', () => {
it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess ', done => { it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess ', done => {
mock.onPut(mockedState.endpoint).replyOnce(200); const featureFlag = {
name: 'feature_flag',
description: 'feature flag',
scopes: [
{
id: '1',
environmentScope: '*',
active: true,
shouldBeDestroyed: false,
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
},
],
version: LEGACY_FLAG,
active: true,
};
mock.onPut(mockedState.endpoint, mapFromScopesViewModel(featureFlag)).replyOnce(200);
testAction( testAction(
updateFeatureFlag, updateFeatureFlag,
{ featureFlag,
name: 'feature_flag', mockedState,
description: 'feature flag', [],
scopes: [{ environmentScope: '*', active: true }], [
}, {
type: 'requestUpdateFeatureFlag',
},
{
type: 'receiveUpdateFeatureFlagSuccess',
},
],
done,
);
});
it('handles new version flags as well', done => {
const featureFlag = {
name: 'name',
description: 'description',
active: true,
version: NEW_VERSION_FLAG,
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
id: 1,
scopes: [{ id: 1, environmentScope: 'environmentScope', shouldBeDestroyed: false }],
shouldBeDestroyed: false,
},
],
};
mock.onPut(mockedState.endpoint, mapStrategiesToRails(featureFlag)).replyOnce(200);
testAction(
updateFeatureFlag,
featureFlag,
mockedState, mockedState,
[], [],
[ [
......
...@@ -45,8 +45,10 @@ describe('Feature flags Edit Module Mutations', () => { ...@@ -45,8 +45,10 @@ describe('Feature flags Edit Module Mutations', () => {
description: 'All environments', description: 'All environments',
scopes: [{ id: 1 }], scopes: [{ id: 1 }],
iid: 5, iid: 5,
version: 2, version: 'new_version_flag',
strategies: [{ scopes: [], type: 'default', paramters: {} }], strategies: [
{ id: 1, scopes: [{ environment_scope: '*' }], name: 'default', parameters: {} },
],
}; };
beforeEach(() => { beforeEach(() => {
...@@ -78,11 +80,19 @@ describe('Feature flags Edit Module Mutations', () => { ...@@ -78,11 +80,19 @@ describe('Feature flags Edit Module Mutations', () => {
}); });
it('should set the version to the provided one', () => { it('should set the version to the provided one', () => {
expect(stateCopy.version).toBe(2); expect(stateCopy.version).toBe('new_version_flag');
}); });
it('should set the strategies to the provided one', () => { it('should set the strategies to the provided one', () => {
expect(stateCopy.strategies).toEqual([{ scopes: [], type: 'default', paramters: {} }]); expect(stateCopy.strategies).toEqual([
{
id: 1,
scopes: [{ environmentScope: '*', shouldBeDestroyed: false }],
name: 'default',
parameters: {},
shouldBeDestroyed: false,
},
]);
}); });
}); });
......
...@@ -413,7 +413,7 @@ describe('feature flags helpers spec', () => { ...@@ -413,7 +413,7 @@ describe('feature flags helpers spec', () => {
name: 'default', name: 'default',
parameters: {}, parameters: {},
_destroy: true, _destroy: true,
scopes: [ scopes_attributes: [
{ {
environment_scope: '*', environment_scope: '*',
id: '1', id: '1',
...@@ -450,7 +450,7 @@ describe('feature flags helpers spec', () => { ...@@ -450,7 +450,7 @@ describe('feature flags helpers spec', () => {
id: '1', id: '1',
name: 'default', name: 'default',
parameters: {}, parameters: {},
scopes: [ scopes_attributes: [
{ {
environment_scope: '*', environment_scope: '*',
}, },
......
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