Commit 21c7e932 authored by Andrew Fontaine's avatar Andrew Fontaine

Remove Frontend to Edit Legacy Flags

Until recently, this has all been considered read-only. We are now in a
position to remove the extra code to help edit legacy flags, which
includes the ability to display them as read-only.

Changelog: removed
parent a7cd42f0
<script>
import { GlAlert, GlLoadingIcon, GlToggle } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import axios from '~/lib/utils/axios_utils';
import { sprintf, s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { LEGACY_FLAG } from '../constants';
import FeatureFlagForm from './form.vue';
export default {
......@@ -15,59 +13,29 @@ export default {
FeatureFlagForm,
},
mixins: [glFeatureFlagMixin()],
inject: {
showUserCallout: {},
userCalloutId: {
default: '',
},
userCalloutsPath: {
default: '',
},
},
data() {
return {
userShouldSeeNewFlagAlert: this.showUserCallout,
};
},
translations: {
legacyReadOnlyFlagAlert: s__(
'FeatureFlags|GitLab is moving to a new way of managing feature flags. This feature flag is read-only, and it will be removed in 14.0. Please create a new feature flag.',
),
},
computed: {
...mapState([
'path',
'error',
'name',
'description',
'scopes',
'strategies',
'isLoading',
'hasError',
'iid',
'active',
'version',
]),
title() {
return this.iid
? `^${this.iid} ${this.name}`
: sprintf(s__('Edit %{name}'), { name: this.name });
},
deprecated() {
return this.version === LEGACY_FLAG;
},
},
created() {
return this.fetchFeatureFlag();
},
methods: {
...mapActions(['updateFeatureFlag', 'fetchFeatureFlag', 'toggleActive']),
dismissNewVersionFlagAlert() {
this.userShouldSeeNewFlagAlert = false;
axios.post(this.userCalloutsPath, {
feature_name: this.userCalloutId,
});
},
},
};
</script>
......@@ -76,9 +44,6 @@ export default {
<gl-loading-icon v-if="isLoading" size="xl" class="gl-mt-7" />
<template v-else-if="!isLoading && !hasError">
<gl-alert v-if="deprecated" variant="warning" :dismissible="false" class="gl-my-5">{{
$options.translations.legacyReadOnlyFlagAlert
}}</gl-alert>
<div class="gl-display-flex gl-align-items-center gl-mb-4 gl-mt-4">
<gl-toggle
:value="active"
......@@ -100,12 +65,10 @@ export default {
<feature-flag-form
:name="name"
:description="description"
:scopes="scopes"
:strategies="strategies"
:cancel-path="path"
:submit-text="__('Save changes')"
:active="active"
:version="version"
@handleSubmit="(data) => updateFeatureFlag(data)"
/>
</template>
......
import Vue from 'vue';
import Vuex from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import EditFeatureFlag from './components/edit_feature_flag.vue';
import createStore from './store/edit';
......@@ -16,9 +15,6 @@ export default () => {
environmentsEndpoint,
projectId,
featureFlagIssuesEndpoint,
userCalloutsPath,
userCalloutId,
showUserCallout,
} = el.dataset;
return new Vue({
......@@ -30,9 +26,6 @@ export default () => {
environmentsEndpoint,
projectId,
featureFlagIssuesEndpoint,
userCalloutsPath,
userCalloutId,
showUserCallout: parseBoolean(showUserCallout),
},
render(createElement) {
return createElement(EditFeatureFlag);
......
......@@ -2,8 +2,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { NEW_VERSION_FLAG } from '../../constants';
import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers';
import { mapStrategiesToRails } from '../helpers';
import * as types from './mutation_types';
/**
......@@ -19,12 +18,7 @@ export const updateFeatureFlag = ({ state, dispatch }, params) => {
dispatch('requestUpdateFeatureFlag');
axios
.put(
state.endpoint,
params.version === NEW_VERSION_FLAG
? mapStrategiesToRails(params)
: mapFromScopesViewModel(params),
)
.put(state.endpoint, mapStrategiesToRails(params))
.then(() => {
dispatch('receiveUpdateFeatureFlagSuccess');
visitUrl(state.path);
......
import { LEGACY_FLAG } from '../../constants';
import { mapToScopesViewModel, mapStrategiesToViewModel } from '../helpers';
import { mapStrategiesToViewModel } from '../helpers';
import * as types from './mutation_types';
export default {
......@@ -14,7 +14,6 @@ export default {
state.description = response.description;
state.iid = response.iid;
state.active = response.active;
state.scopes = mapToScopesViewModel(response.scopes);
state.strategies = mapStrategiesToViewModel(response.strategies);
state.version = response.version || LEGACY_FLAG;
},
......
import { isEmpty, uniqueId, isString } from 'lodash';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
INTERNAL_ID_PREFIX,
DEFAULT_PERCENT_ROLLOUT,
PERCENT_ROLLOUT_GROUP_ID,
fetchPercentageParams,
fetchUserIdParams,
LEGACY_FLAG,
} from '../constants';
/**
* Converts raw scope objects fetched from the API into an array of scope
* objects that is easier/nicer to bind to in Vue.
* @param {Array} scopesFromRails An array of scope objects fetched from the API
*/
export const mapToScopesViewModel = (scopesFromRails) =>
(scopesFromRails || []).map((s) => {
const percentStrategy = (s.strategies || []).find(
(strat) => strat.name === ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
);
const rolloutPercentage = fetchPercentageParams(percentStrategy) || DEFAULT_PERCENT_ROLLOUT;
const userStrategy = (s.strategies || []).find(
(strat) => strat.name === ROLLOUT_STRATEGY_USER_ID,
);
const rolloutStrategy =
(percentStrategy && percentStrategy.name) ||
(userStrategy && userStrategy.name) ||
ROLLOUT_STRATEGY_ALL_USERS;
const rolloutUserIds = (fetchUserIdParams(userStrategy) || '')
.split(',')
.filter((id) => id)
.join(', ');
return {
id: s.id,
environmentScope: s.environment_scope,
active: Boolean(s.active),
canUpdate: Boolean(s.can_update),
protected: Boolean(s.protected),
rolloutStrategy,
rolloutPercentage,
rolloutUserIds,
// eslint-disable-next-line no-underscore-dangle
shouldBeDestroyed: Boolean(s._destroy),
shouldIncludeUserIds: rolloutUserIds.length > 0 && percentStrategy !== null,
};
});
/**
* Converts the parameters emitted by the Vue component into
* the shape that the Rails API expects.
* @param {Array} scopesFromVue An array of scope objects from the Vue component
*/
export const mapFromScopesViewModel = (params) => {
const scopes = (params.scopes || []).map((s) => {
const parameters = {};
if (s.rolloutStrategy === ROLLOUT_STRATEGY_PERCENT_ROLLOUT) {
parameters.groupId = PERCENT_ROLLOUT_GROUP_ID;
parameters.percentage = s.rolloutPercentage;
} else if (s.rolloutStrategy === ROLLOUT_STRATEGY_USER_ID) {
parameters.userIds = (s.rolloutUserIds || '').replace(/, /g, ',');
}
const userIdParameters = {};
if (s.shouldIncludeUserIds && s.rolloutStrategy !== ROLLOUT_STRATEGY_USER_ID) {
userIdParameters.userIds = (s.rolloutUserIds || '').replace(/, /g, ',');
}
// Strip out any internal IDs
const id = isString(s.id) && s.id.startsWith(INTERNAL_ID_PREFIX) ? undefined : s.id;
const strategies = [
{
name: s.rolloutStrategy,
parameters,
},
];
if (!isEmpty(userIdParameters)) {
strategies.push({ name: ROLLOUT_STRATEGY_USER_ID, parameters: userIdParameters });
}
return {
id,
environment_scope: s.environmentScope,
active: s.active,
can_update: s.canUpdate,
protected: s.protected,
_destroy: s.shouldBeDestroyed,
strategies,
};
});
const model = {
operations_feature_flag: {
name: params.name,
description: params.description,
active: params.active,
scopes_attributes: scopes,
version: LEGACY_FLAG,
},
};
return model;
};
/**
* Creates a new feature flag environment scope object for use
* in a Vue component. An optional parameter can be passed to
* override the property values that are created by default.
*
* @param {Object} overrides An optional object whose
* property values will be used to override the default values.
*
*/
export const createNewEnvironmentScope = (overrides = {}, featureFlagPermissions = false) => {
const defaultScope = {
environmentScope: '',
active: false,
id: uniqueId(INTERNAL_ID_PREFIX),
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
};
const newScope = {
...defaultScope,
...overrides,
};
if (featureFlagPermissions) {
newScope.canUpdate = true;
newScope.protected = false;
}
return newScope;
};
import { ROLLOUT_STRATEGY_GITLAB_USER_LIST, NEW_VERSION_FLAG } from '../constants';
const mapStrategyScopesToRails = (scopes) =>
scopes.length === 0
......@@ -206,8 +61,8 @@ export const mapStrategiesToRails = (params) => ({
operations_feature_flag: {
name: params.name,
description: params.description,
version: params.version,
active: params.active,
strategies_attributes: (params.strategies || []).map(mapStrategyToRails),
version: NEW_VERSION_FLAG,
},
});
......@@ -13,10 +13,6 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
before_action :ensure_flag_writable!, only: [:update]
before_action :exclude_legacy_flags_check, only: [:edit]
before_action do
push_frontend_feature_flag(:feature_flag_permissions)
end
feature_category :feature_flags
def index
......
......@@ -8,9 +8,6 @@
project_id: @project.id,
feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments/index.md', anchor: 'scoping-environments-with-specs'),
feature_flag_issues_endpoint: feature_flag_issues_links_endpoint(@project, @feature_flag, current_user) } }
---
name: feature_flag_permissions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/10096
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254981
milestone: '11.10'
type: development
group: group::release
default_enabled: false
......@@ -13,9 +13,6 @@
.form-group
= f.label :deploy_access_levels_attributes, class: 'label-bold' do
= s_('ProtectedEnvironment|Allowed to deploy')
- if Feature.enabled?(:feature_flag_permissions, @project)
.text-muted.mb-2
= s_('ProtectedEnvironment|Select users to deploy and manage Feature Flag settings')
= render partial: 'projects/protected_environments/deploy_access_levels_dropdown', locals: { f: f }
.card-footer
......
......@@ -10,7 +10,6 @@ RSpec.describe 'User creates feature flag', :js do
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
end
......
......@@ -15,7 +15,6 @@ RSpec.describe 'User deletes feature flag', :js do
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
visit(project_feature_flags_path(project))
......
......@@ -13643,9 +13643,6 @@ msgstr ""
msgid "FeatureFlags|Get started with feature flags"
msgstr ""
msgid "FeatureFlags|GitLab is moving to a new way of managing feature flags. This feature flag is read-only, and it will be removed in 14.0. Please create a new feature flag."
msgstr ""
msgid "FeatureFlags|ID"
msgstr ""
......@@ -26396,9 +26393,6 @@ msgstr ""
msgid "ProtectedEnvironment|Select users"
msgstr ""
msgid "ProtectedEnvironment|Select users to deploy and manage Feature Flag settings"
msgstr ""
msgid "ProtectedEnvironment|There are currently no protected environments. Protect an environment with this form."
msgstr ""
......
......@@ -10,7 +10,6 @@ RSpec.describe 'User creates feature flag', :js do
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
end
......
......@@ -15,7 +15,6 @@ RSpec.describe 'User deletes feature flag', :js do
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
visit(project_feature_flags_path(project))
......
......@@ -13,9 +13,6 @@ RSpec.describe 'User updates feature flag', :js do
end
before do
stub_feature_flags(
feature_flag_permissions: false
)
sign_in(user)
end
......
import { GlToggle, GlAlert } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import { TEST_HOST } from 'spec/test_constants';
import EditFeatureFlag from '~/feature_flags/components/edit_feature_flag.vue';
import Form from '~/feature_flags/components/form.vue';
import { LEGACY_FLAG, NEW_VERSION_FLAG } from '~/feature_flags/constants';
import createStore from '~/feature_flags/store/edit';
import axios from '~/lib/utils/axios_utils';
const localVue = createLocalVue();
localVue.use(Vuex);
const userCalloutId = 'feature_flags_new_version';
const userCalloutsPath = `${TEST_HOST}/user_callouts`;
Vue.use(Vuex);
describe('Edit feature flag form', () => {
let wrapper;
let mock;
......@@ -25,20 +20,14 @@ describe('Edit feature flag form', () => {
endpoint: `${TEST_HOST}/feature_flags.json`,
});
const factory = (opts = {}) => {
const factory = (provide = {}) => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
wrapper = shallowMount(EditFeatureFlag, {
localVue,
store,
provide: {
showUserCallout: true,
userCalloutId,
userCalloutsPath,
...opts,
},
provide,
});
};
......@@ -52,18 +41,8 @@ describe('Edit feature flag form', () => {
updated_at: '2019-01-17T17:27:39.778Z',
name: 'feature_flag',
description: '',
version: LEGACY_FLAG,
edit_path: '/h5bp/html5-boilerplate/-/feature_flags/21/edit',
destroy_path: '/h5bp/html5-boilerplate/-/feature_flags/21',
scopes: [
{
id: 21,
active: false,
environment_scope: '*',
created_at: '2019-01-17T17:27:39.778Z',
updated_at: '2019-01-17T17:27:39.778Z',
},
],
});
factory();
setImmediate(() => done());
......@@ -74,9 +53,7 @@ describe('Edit feature flag form', () => {
mock.restore();
});
const findAlert = () => wrapper.find(GlAlert);
const findWarningGlAlert = () =>
wrapper.findAll(GlAlert).filter((c) => c.props('variant') === 'warning');
const findWarningGlAlert = () => wrapper.findComponent(GlAlert);
it('should display the iid', () => {
expect(wrapper.find('h3').text()).toContain('^5');
......@@ -86,21 +63,13 @@ describe('Edit feature flag form', () => {
expect(wrapper.find(GlToggle).exists()).toBe(true);
});
it('should set the value of the toggle to whether or not the flag is active', () => {
expect(wrapper.find(GlToggle).props('value')).toBe(true);
});
it('should alert users the flag is read-only', () => {
expect(findAlert().text()).toContain('GitLab is moving to a new way of managing feature flags');
});
describe('with error', () => {
it('should render the error', () => {
store.dispatch('receiveUpdateFeatureFlagError', { message: ['The name is required'] });
return wrapper.vm.$nextTick(() => {
const warningGlAlert = findWarningGlAlert();
expect(warningGlAlert.at(1).exists()).toEqual(true);
expect(warningGlAlert.at(1).text()).toContain('The name is required');
expect(warningGlAlert.exists()).toEqual(true);
expect(warningGlAlert.text()).toContain('The name is required');
});
});
});
......@@ -114,32 +83,6 @@ describe('Edit feature flag form', () => {
expect(wrapper.find(Form).exists()).toEqual(true);
});
it('should set the version of the form from the feature flag', () => {
expect(wrapper.find(Form).attributes('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).attributes('version')).toBe(NEW_VERSION_FLAG);
});
});
it('should track when the toggle is clicked', () => {
const toggle = wrapper.find(GlToggle);
const spy = mockTracking('_category_', toggle.element, jest.spyOn);
......
......@@ -16,86 +16,24 @@ export const featureFlag = {
destroy_path: 'feature_flags/1',
update_path: 'feature_flags/1',
edit_path: 'feature_flags/1/edit',
scopes: [
strategies: [
{
id: 1,
active: true,
environment_scope: '*',
can_update: true,
protected: false,
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
},
],
id: 9,
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
scopes: [{ id: 17, environment_scope: '*' }],
},
{
id: 2,
active: false,
environment_scope: 'production',
can_update: true,
protected: false,
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
},
],
id: 8,
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: {},
scopes: [{ id: 18, environment_scope: 'review/*' }],
},
{
id: 3,
active: false,
environment_scope: 'review/*',
can_update: true,
protected: false,
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
},
],
},
{
id: 4,
active: true,
environment_scope: 'development',
can_update: true,
protected: false,
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
strategies: [
{
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: {
percentage: '86',
},
},
],
},
{
id: 5,
active: true,
environment_scope: 'development',
can_update: true,
protected: false,
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
strategies: [
{
name: ROLLOUT_STRATEGY_FLEXIBLE_ROLLOUT,
parameters: {
rollout: '42',
stickiness: 'DEFAULT',
},
},
],
id: 7,
name: ROLLOUT_STRATEGY_USER_ID,
parameters: { userIds: '1,2,3,4' },
scopes: [{ id: 19, environment_scope: 'production' }],
},
],
};
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import {
NEW_VERSION_FLAG,
LEGACY_FLAG,
ROLLOUT_STRATEGY_ALL_USERS,
} from '~/feature_flags/constants';
import { ROLLOUT_STRATEGY_ALL_USERS } from '~/feature_flags/constants';
import {
updateFeatureFlag,
requestUpdateFeatureFlag,
......@@ -19,7 +15,7 @@ import {
} from '~/feature_flags/store/edit/actions';
import * as types from '~/feature_flags/store/edit/mutation_types';
import state from '~/feature_flags/store/edit/state';
import { mapStrategiesToRails, mapFromScopesViewModel } from '~/feature_flags/store/helpers';
import { mapStrategiesToRails } from '~/feature_flags/store/helpers';
import axios from '~/lib/utils/axios_utils';
jest.mock('~/lib/utils/url_utility');
......@@ -45,47 +41,10 @@ describe('Feature flags Edit Module actions', () => {
describe('success', () => {
it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess ', (done) => {
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(
updateFeatureFlag,
featureFlag,
mockedState,
[],
[
{
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,
......
import { uniqueId } from 'lodash';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
PERCENT_ROLLOUT_GROUP_ID,
INTERNAL_ID_PREFIX,
DEFAULT_PERCENT_ROLLOUT,
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
import {
mapToScopesViewModel,
mapFromScopesViewModel,
createNewEnvironmentScope,
mapStrategiesToViewModel,
mapStrategiesToRails,
} from '~/feature_flags/store/helpers';
import { NEW_VERSION_FLAG } from '~/feature_flags/constants';
import { mapStrategiesToViewModel, mapStrategiesToRails } from '~/feature_flags/store/helpers';
describe('feature flags helpers spec', () => {
describe('mapToScopesViewModel', () => {
it('converts the data object from the Rails API into something more usable by Vue', () => {
const input = [
{
id: 3,
environment_scope: 'environment_scope',
active: true,
can_update: true,
protected: true,
strategies: [
{
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: {
percentage: '56',
},
},
{
name: ROLLOUT_STRATEGY_USER_ID,
parameters: {
userIds: '123,234',
},
},
],
_destroy: true,
},
];
const expected = [
expect.objectContaining({
id: 3,
environmentScope: 'environment_scope',
active: true,
canUpdate: true,
protected: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '56',
rolloutUserIds: '123, 234',
shouldBeDestroyed: true,
}),
];
const actual = mapToScopesViewModel(input);
expect(actual).toEqual(expected);
});
it('returns Boolean properties even when their Rails counterparts were not provided (are `undefined`)', () => {
const input = [
{
id: 3,
environment_scope: 'environment_scope',
},
];
const [result] = mapToScopesViewModel(input);
expect(result).toEqual(
expect.objectContaining({
active: false,
canUpdate: false,
protected: false,
shouldBeDestroyed: false,
}),
);
});
it('returns an empty array if null or undefined is provided as a parameter', () => {
expect(mapToScopesViewModel(null)).toEqual([]);
expect(mapToScopesViewModel(undefined)).toEqual([]);
});
describe('with user IDs per environment', () => {
let oldGon;
beforeEach(() => {
oldGon = window.gon;
window.gon = { features: { featureFlagsUsersPerEnvironment: true } };
});
afterEach(() => {
window.gon = oldGon;
});
it('sets the user IDs as a comma separated string', () => {
const input = [
{
id: 3,
environment_scope: 'environment_scope',
active: true,
can_update: true,
protected: true,
strategies: [
{
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: {
percentage: '56',
},
},
{
name: ROLLOUT_STRATEGY_USER_ID,
parameters: {
userIds: '123,234',
},
},
],
_destroy: true,
},
];
const expected = [
{
id: 3,
environmentScope: 'environment_scope',
active: true,
canUpdate: true,
protected: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '56',
rolloutUserIds: '123, 234',
shouldBeDestroyed: true,
shouldIncludeUserIds: true,
},
];
const actual = mapToScopesViewModel(input);
expect(actual).toEqual(expected);
});
});
});
describe('mapFromScopesViewModel', () => {
it('converts the object emitted from the Vue component into an object than is in the right format to be submitted to the Rails API', () => {
const input = {
name: 'name',
description: 'description',
active: true,
scopes: [
{
id: 4,
environmentScope: 'environmentScope',
active: true,
canUpdate: true,
protected: true,
shouldBeDestroyed: true,
shouldIncludeUserIds: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '48',
rolloutUserIds: '123, 234',
},
],
};
const expected = {
operations_feature_flag: {
name: 'name',
description: 'description',
active: true,
version: LEGACY_FLAG,
scopes_attributes: [
{
id: 4,
environment_scope: 'environmentScope',
active: true,
can_update: true,
protected: true,
_destroy: true,
strategies: [
{
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: {
groupId: PERCENT_ROLLOUT_GROUP_ID,
percentage: '48',
},
},
{
name: ROLLOUT_STRATEGY_USER_ID,
parameters: {
userIds: '123,234',
},
},
],
},
],
},
};
const actual = mapFromScopesViewModel(input);
expect(actual).toEqual(expected);
});
it('should strip out internal IDs', () => {
const input = {
scopes: [{ id: 3 }, { id: uniqueId(INTERNAL_ID_PREFIX) }],
};
const result = mapFromScopesViewModel(input);
const [realId, internalId] = result.operations_feature_flag.scopes_attributes;
expect(realId.id).toBe(3);
expect(internalId.id).toBeUndefined();
});
it('returns scopes_attributes as [] if param.scopes is null or undefined', () => {
let {
operations_feature_flag: { scopes_attributes: actualScopes },
} = mapFromScopesViewModel({ scopes: null });
expect(actualScopes).toEqual([]);
({
operations_feature_flag: { scopes_attributes: actualScopes },
} = mapFromScopesViewModel({ scopes: undefined }));
expect(actualScopes).toEqual([]);
});
describe('with user IDs per environment', () => {
it('sets the user IDs as a comma separated string', () => {
const input = {
name: 'name',
description: 'description',
active: true,
scopes: [
{
id: 4,
environmentScope: 'environmentScope',
active: true,
canUpdate: true,
protected: true,
shouldBeDestroyed: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '48',
rolloutUserIds: '123, 234',
shouldIncludeUserIds: true,
},
],
};
const expected = {
operations_feature_flag: {
name: 'name',
description: 'description',
version: LEGACY_FLAG,
active: true,
scopes_attributes: [
{
id: 4,
environment_scope: 'environmentScope',
active: true,
can_update: true,
protected: true,
_destroy: true,
strategies: [
{
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: {
groupId: PERCENT_ROLLOUT_GROUP_ID,
percentage: '48',
},
},
{
name: ROLLOUT_STRATEGY_USER_ID,
parameters: {
userIds: '123,234',
},
},
],
},
],
},
};
const actual = mapFromScopesViewModel(input);
expect(actual).toEqual(expected);
});
});
});
describe('createNewEnvironmentScope', () => {
it('should return a new environment scope object populated with the default options', () => {
const expected = {
environmentScope: '',
active: false,
id: expect.stringContaining(INTERNAL_ID_PREFIX),
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
};
const actual = createNewEnvironmentScope();
expect(actual).toEqual(expected);
});
it('should return a new environment scope object with overrides applied', () => {
const overrides = {
environmentScope: 'environmentScope',
active: true,
};
const expected = {
environmentScope: 'environmentScope',
active: true,
id: expect.stringContaining(INTERNAL_ID_PREFIX),
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
};
const actual = createNewEnvironmentScope(overrides);
expect(actual).toEqual(expected);
});
it('sets canUpdate and protected when called with featureFlagPermissions=true', () => {
expect(createNewEnvironmentScope({}, true)).toEqual(
expect.objectContaining({
canUpdate: true,
protected: false,
}),
);
});
});
describe('mapStrategiesToViewModel', () => {
it('should map rails casing to view model casing', () => {
expect(
......@@ -380,14 +36,14 @@ describe('feature flags helpers spec', () => {
});
it('inserts spaces between user ids', () => {
const strategy = mapStrategiesToViewModel([
const [strategy] = mapStrategiesToViewModel([
{
id: '1',
name: 'userWithId',
parameters: { userIds: 'user1,user2,user3' },
scopes: [],
},
])[0];
]);
expect(strategy.parameters).toEqual({ userIds: 'user1, user2, user3' });
});
......@@ -399,7 +55,6 @@ describe('feature flags helpers spec', () => {
mapStrategiesToRails({
name: 'test',
description: 'test description',
version: NEW_VERSION_FLAG,
active: true,
strategies: [
{
......@@ -421,8 +76,8 @@ describe('feature flags helpers spec', () => {
operations_feature_flag: {
name: 'test',
description: 'test description',
version: NEW_VERSION_FLAG,
active: true,
version: NEW_VERSION_FLAG,
strategies_attributes: [
{
id: '1',
......@@ -447,7 +102,6 @@ describe('feature flags helpers spec', () => {
mapStrategiesToRails({
name: 'test',
description: 'test description',
version: NEW_VERSION_FLAG,
active: true,
strategies: [
{
......@@ -462,8 +116,8 @@ describe('feature flags helpers spec', () => {
operations_feature_flag: {
name: 'test',
description: 'test description',
version: NEW_VERSION_FLAG,
active: true,
version: NEW_VERSION_FLAG,
strategies_attributes: [
{
id: '1',
......@@ -483,7 +137,6 @@ describe('feature flags helpers spec', () => {
it('removes white space between user ids', () => {
const result = mapStrategiesToRails({
name: 'test',
version: NEW_VERSION_FLAG,
active: true,
strategies: [
{
......@@ -503,7 +156,6 @@ describe('feature flags helpers spec', () => {
it('preserves the value of active', () => {
const result = mapStrategiesToRails({
name: 'test',
version: NEW_VERSION_FLAG,
active: false,
strategies: [],
});
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { ROLLOUT_STRATEGY_ALL_USERS, NEW_VERSION_FLAG } from '~/feature_flags/constants';
import { ROLLOUT_STRATEGY_ALL_USERS } from '~/feature_flags/constants';
import { mapStrategiesToRails } from '~/feature_flags/store/helpers';
import {
createFeatureFlag,
requestCreateFeatureFlag,
......@@ -39,7 +38,6 @@ describe('Feature flags New Module Actions', () => {
name: 'name',
description: 'description',
active: true,
version: NEW_VERSION_FLAG,
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
......@@ -76,7 +74,6 @@ describe('Feature flags New Module Actions', () => {
name: 'name',
description: 'description',
active: true,
version: NEW_VERSION_FLAG,
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
......
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