Commit bb1c15a3 authored by Filipa Lacerda's avatar Filipa Lacerda Committed by Phil Hughes

Renders new & edit feature flag page in Vue

parent da9e64e4
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { createNamespacedHelpers } from 'vuex';
import { sprintf, s__ } from '~/locale';
import store from '../store/index';
import FeatureFlagForm from './form.vue';
const { mapState, mapActions } = createNamespacedHelpers('edit');
export default {
store,
components: {
GlLoadingIcon,
FeatureFlagForm,
},
props: {
endpoint: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
...mapState(['error', 'name', 'description', 'scopes', 'isLoading', 'hasError']),
title() {
return sprintf(s__('Edit %{name}'), { name: this.name });
},
},
created() {
this.setPath(this.path);
return this.setEndpoint(this.endpoint).then(() => this.fetchFeatureFlag());
},
methods: {
...mapActions(['updateFeatureFlag', 'setEndpoint', 'setPath', 'fetchFeatureFlag']),
},
};
</script>
<template>
<div>
<gl-loading-icon v-if="isLoading" />
<template v-else-if="!isLoading && !hasError">
<h3 class="page-title">{{ title }}</h3>
<div v-if="error.length" class="alert alert-danger">
<p v-for="(message, index) in error" :key="index" class="mb-0">{{ message }}</p>
</div>
<feature-flag-form
:name="name"
:description="description"
:scopes="scopes"
:cancel-path="path"
:submit-text="__('Save changes')"
@handleSubmit="data => updateFeatureFlag(data)"
/>
</template>
</div>
</template>
......@@ -118,7 +118,7 @@ export default {
{{ s__('FeatureFlags|Environment Specs') }}
</div>
<div
class="table-mobile-content d-flex flex-wrap justify-content-end js-feature-flag-environments"
class="table-mobile-content d-flex flex-wrap justify-content-end justify-content-md-start js-feature-flag-environments"
>
<span
v-for="scope in featureFlag.scopes"
......
<script>
import _ from 'underscore';
import { GlButton } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
GlButton,
ToggleButton,
Icon,
},
props: {
name: {
type: String,
required: false,
default: '',
},
description: {
type: String,
required: false,
default: '',
},
scopes: {
type: Array,
required: false,
default: () => [],
},
cancelPath: {
type: String,
required: true,
},
submitText: {
type: String,
required: true,
},
},
data() {
return {
formName: this.name,
formDescription: this.description,
formScopes: this.scopes || [],
newScope: '',
};
},
helpText: sprintf(
s__(
'FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}.',
),
{
codeStart: '<code>',
codeEnd: '</code>',
boldStart: '<b>',
boldEnd: '</b>',
},
false,
),
allEnvironments: s__('FeatureFlags|* (All Environments)'),
all: '*',
computed: {
filteredScopes() {
// eslint-disable-next-line no-underscore-dangle
return this.formScopes.filter(scope => !scope._destroy);
},
},
watch: {
newScope(newVal) {
if (!_.isEmpty(newVal)) {
this.addNewScope();
}
},
},
methods: {
isAllEnvironment(name) {
return name === this.$options.all;
},
/**
* When the user updates the status of
* an existing scope we toggle the status for
* the `formScopes`
*/
onUpdateScopeStatus(scope, index, status) {
this.formScopes.splice(index, 1, Object.assign({}, scope, { active: status }));
},
addNewScope() {
const uniqueId = _.uniqueId('scope_');
this.formScopes.push({ environment_scope: this.newScope, active: false, uniqueId });
this.$nextTick(() => {
this.$refs[uniqueId][0].focus();
this.newScope = '';
});
},
/**
* When the user clicks the toggle button in the new row,
* we automatically add it as a new scope
*
* @param {Boolean} value the toggle value
*/
onChangeNewScopeStatus(value) {
this.formScopes.push({
active: value,
environment_scope: this.newScope,
});
this.newScope = '';
},
/**
* When the user clicks the remove button we delete the scope
*
* If the scope has an ID, we need to add the `_destroy` flag
* otherwise we can just remove it.
* Backend needs the destroy flag only in the PUT request.
*/
removeScope(index, scope) {
if (scope.id) {
this.formScopes.splice(index, 1, Object.assign({}, scope, { _destroy: true }));
} else {
this.formScopes.splice(index, 1);
}
},
handleSubmit() {
this.$emit('handleSubmit', {
name: this.formName,
description: this.formDescription,
scopes: this.formScopes,
});
},
},
};
</script>
<template>
<form>
<fieldset>
<div class="row">
<div class="form-group col-md-4">
<label for="feature-flag-name" class="label-bold">{{ s__('FeatureFlags|Name') }}</label>
<input id="feature-flag-name" v-model="formName" class="form-control" />
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
<label for="feature-flag-description" class="label-bold">
{{ s__('FeatureFlags|Description') }}
</label>
<textarea
id="feature-flag-description"
v-model="formDescription"
class="form-control"
rows="4"
></textarea>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<h4>{{ s__('FeatureFlags|Target environments') }}</h4>
<div v-html="$options.helpText"></div>
<div class="js-scopes-table table-holder prepend-top-default">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-60" role="columnheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-section section-20" role="columnheader">
{{ s__('FeatureFlags|Status') }}
</div>
</div>
<div
v-for="(scope, index) in filteredScopes"
:key="scope.id"
class="gl-responsive-table-row"
role="row"
>
<div class="table-section section-60" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-mobile-content js-feature-flag-status">
<p v-if="isAllEnvironment(scope.environment_scope)" class="js-scope-all">
{{ $options.allEnvironments }}
</p>
<input
v-else
:ref="scope.uniqueId"
v-model="scope.environment_scope"
type="text"
class="form-control col-md-6 prepend-left-4"
/>
</div>
</div>
<div class="table-section section-20" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Status') }}
</div>
<div class="table-mobile-content js-feature-flag-status">
<toggle-button
:value="scope.active"
@change="status => onUpdateScopeStatus(scope, index, status)"
/>
</div>
</div>
<div class="table-section section-20" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Status') }}
</div>
<div class="table-mobile-content js-feature-flag-delete">
<gl-button
v-if="!isAllEnvironment(scope.environment_scope)"
class="js-delete-scope btn-transparent"
@click="removeScope(index, scope)"
>
<icon name="clear" />
</gl-button>
</div>
</div>
</div>
<div class="js-add-new-scope gl-responsive-table-row" role="row">
<div class="table-section section-60" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-mobile-content js-feature-flag-status">
<input
v-model="newScope"
type="text"
class="js-new-scope-name form-control col-md-6 prepend-left-4"
/>
</div>
</div>
<div class="table-section section-20" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Status') }}
</div>
<div class="table-mobile-content js-feature-flag-status">
<toggle-button :value="false" @change="onChangeNewScopeStatus" />
</div>
</div>
</div>
</div>
</div>
</div>
</fieldset>
<div class="form-actions">
<gl-button
type="button"
variant="success"
class="js-ff-submit col-xs-12"
@click="handleSubmit"
>{{ submitText }}</gl-button
>
<gl-button
:href="cancelPath"
variant="secondary"
class="js-ff-cancel col-xs-12 float-right"
>{{ __('Cancel') }}</gl-button
>
</div>
</form>
</template>
<script>
import { createNamespacedHelpers } from 'vuex';
import store from '../store/index';
import FeatureFlagForm from './form.vue';
const { mapState, mapActions } = createNamespacedHelpers('new');
export default {
store,
components: {
FeatureFlagForm,
},
props: {
endpoint: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
...mapState(['error']),
scopes() {
return [
{
environment_scope: '*',
active: true,
},
];
},
},
created() {
this.setEndpoint(this.endpoint);
this.setPath(this.path);
},
methods: {
...mapActions(['createFeatureFlag', 'setEndpoint', 'setPath']),
},
};
</script>
<template>
<div>
<h3 class="page-title">{{ s__('FeatureFlags|New Feature Flag') }}</h3>
<div v-if="error.length" class="alert alert-danger">
<p v-for="(message, index) in error" :key="index" class="mb-0">{{ message }}</p>
</div>
<feature-flag-form
:cancel-path="path"
:submit-text="s__('FeatureFlags|Create feature flag')"
:scopes="scopes"
@handleSubmit="data => createFeatureFlag(data)"
/>
</div>
</template>
import Vue from 'vue';
import EditFeatureFlag from 'ee/feature_flags/components/edit_feature_flag.vue';
export default () => {
const el = document.querySelector('#js-edit-feature-flag');
return new Vue({
el,
components: {
EditFeatureFlag,
},
render(createElement) {
return createElement('edit-feature-flag', {
props: {
endpoint: el.dataset.endpoint,
path: el.dataset.featureFlagsPath,
},
});
},
});
};
import Vue from 'vue';
import NewFeatureFlag from 'ee/feature_flags/components/new_feature_flag.vue';
export default () => {
const el = document.querySelector('#js-new-feature-flag');
return new Vue({
el,
components: {
NewFeatureFlag,
},
render(createElement) {
return createElement('new-feature-flag', {
props: {
endpoint: el.dataset.endpoint,
path: el.dataset.featureFlagsPath,
},
});
},
});
};
......@@ -32,7 +32,7 @@ export const updateFeatureFlag = ({ state, dispatch }, params) => {
dispatch('requestUpdateFeatureFlag');
axios
.put(state.endpoint, { params: parseFeatureFlagsParams(params) })
.put(state.endpoint, parseFeatureFlagsParams(params))
.then(() => {
dispatch('receiveUpdateFeatureFlagSuccess');
visitUrl(state.path);
......
......@@ -16,7 +16,9 @@ export default {
state.name = response.name;
state.description = response.description;
state.scopes = state.scopes;
// When there aren't scopes BE sends `null`
state.scopes = response.scopes || [];
},
[types.RECEIVE_FEATURE_FLAG_ERROR](state) {
state.isLoading = false;
......@@ -31,6 +33,6 @@ export default {
},
[types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR](state, error) {
state.isSendingRequest = false;
state.error = error.message;
state.error = error.message || [];
},
};
// eslint-disable-next-line import/prefer-default-export
export const parseFeatureFlagsParams = params => ({
operations_feature_flags: {
operations_feature_flag: {
name: params.name,
description: params.description,
active: true,
scopes_attributes: params.scopes.map(scope => ({
environment_scope: scope.name,
active: scope.active,
})),
// removes uniqueId key used in creation form
scopes_attributes: params.scopes.map(scope => {
const scopeCopy = Object.assign({}, scope);
delete scopeCopy.uniqueId;
return scopeCopy;
}),
},
});
......@@ -30,7 +30,7 @@ export const createFeatureFlag = ({ state, dispatch }, params) => {
dispatch('requestCreateFeatureFlag');
axios
.post(state.endpoint, { params: parseFeatureFlagsParams(params) })
.post(state.endpoint, parseFeatureFlagsParams(params))
.then(() => {
dispatch('receiveCreateFeatureFlagSuccess');
visitUrl(state.path);
......
......@@ -16,6 +16,6 @@ export default {
},
[types.RECEIVE_CREATE_FEATURE_FLAG_ERROR](state, error) {
state.isSendingRequest = false;
state.error = error.message;
state.error = error.message || [];
},
};
import initEditFeatureFlags from 'ee/feature_flags/edit';
if (gon.features && gon.features.featureFlagsEnvironmentScope) {
document.addEventListener('DOMContentLoaded', initEditFeatureFlags);
}
import initNewFeatureFlags from 'ee/feature_flags/new';
if (gon.features && gon.features.featureFlagsEnvironmentScope) {
document.addEventListener('DOMContentLoaded', initNewFeatureFlags);
}
- add_to_breadcrumbs "Feature Flags", project_feature_flags_path(@project)
- add_to_breadcrumbs s_('FeatureFlags|Feature Flags'), project_feature_flags_path(@project)
- breadcrumb_title @feature_flag.name
- page_title s_('FeatureFlags|Edit Feature Flag')
%h3.page-title
- if Feature.enabled?(:feature_flags_environment_scope, @project)
#js-edit-feature-flag{ data: { endpoint: project_feature_flag_path(@project, @feature_flag), feature_flags_path: project_feature_flags_path(@project) } }
- else
%h3.page-title
= s_('FeatureFlags|Edit %{feature_flag_name}') % { feature_flag_name: @feature_flag.name }
%hr.clearfix
%div
%hr.clearfix
%div
= form_for [@project, @feature_flag], url: project_feature_flag_path(@project, @feature_flag), html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
- @breadcrumb_link = new_project_feature_flag_path(@project)
- add_to_breadcrumbs "Feature Flags", project_feature_flags_path(@project)
- add_to_breadcrumbs s_('FeatureFlags|Feature Flags'), project_feature_flags_path(@project)
- breadcrumb_title s_('FeatureFlags|New')
- page_title s_('FeatureFlags|New Feature Flag')
%h3.page-title
- if Feature.enabled?(:feature_flags_environment_scope, @project)
#js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json), feature_flags_path: project_feature_flags_path(@project) } }
- else
%h3.page-title
= s_('FeatureFlags|New Feature Flag')
%hr.clearfix
%div
%hr.clearfix
%div
= form_for [@project, @feature_flag], url: project_feature_flags_path(@project), html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
---
title: Renders New and Edit forms for feature flag in Vue and allow to define scopes
merge_request:
author:
type: changed
......@@ -200,8 +200,6 @@ describe Projects::FeatureFlagsController do
subject
expect(response).to be_ok
expect(response).to render_template('new')
expect(response).to render_template('_form')
end
end
......@@ -318,21 +316,6 @@ describe Projects::FeatureFlagsController do
expect(response).to redirect_to(project_feature_flags_path(project))
end
end
context 'when a feature flag already exists' do
let!(:feature_flag) { create(:operations_feature_flag, project: project, name: 'my_feature_flag') }
let(:params) do
view_params.merge(operations_feature_flag: { name: 'my_feature_flag', active: true })
end
it 'shows an error' do
subject
expect(response).to render_template('new')
expect(response).to render_template('_errors')
end
end
end
describe 'POST create.json' do
......
......@@ -15,6 +15,7 @@ describe 'Feature Flags', :js do
before do
stub_licensed_features(feature_flags: true)
stub_feature_flags(feature_flags_environment_scope: false)
sign_in(user)
end
......
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Form from 'ee/feature_flags/components/form.vue';
import editModule from 'ee/feature_flags/store/modules/edit';
import EditFeatureFlag from 'ee/feature_flags/components/edit_feature_flag.vue';
import { TEST_HOST } from 'spec/test_constants';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Edit feature flag form', () => {
let wrapper;
let mock;
const store = new Vuex.Store({
modules: {
edit: editModule,
},
});
const factory = () => {
wrapper = shallowMount(localVue.extend(EditFeatureFlag), {
localVue,
propsData: {
endpoint: `${TEST_HOST}/feature_flags.json'`,
path: '/feature_flags',
},
store,
sync: false,
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(`${TEST_HOST}/feature_flags.json'`).replyOnce(200, {
id: 21,
active: false,
created_at: '2019-01-17T17:27:39.778Z',
updated_at: '2019-01-17T17:27:39.778Z',
name: 'feature_flag',
description: '',
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();
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
describe('with error', () => {
it('should render the error', done => {
setTimeout(() => {
store.dispatch('edit/receiveUpdateFeatureFlagError', { message: ['The name is required'] });
wrapper.vm.$nextTick(() => {
expect(wrapper.find('.alert-danger').exists()).toEqual(true);
expect(wrapper.find('.alert-danger').text()).toContain('The name is required');
done();
});
}, 0);
});
});
describe('without error', () => {
it('renders form title', done => {
setTimeout(() => {
expect(wrapper.text()).toContain('Edit feature_flag');
done();
}, 0);
});
it('should render feature flag form', done => {
setTimeout(() => {
expect(wrapper.find(Form).exists()).toEqual(true);
done();
}, 0);
});
});
});
import { createLocalVue, mount } from '@vue/test-utils';
import Form from 'ee/feature_flags/components/form.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
describe('feature flag form', () => {
let wrapper;
const requiredProps = {
cancelPath: 'feature_flags',
submitText: 'Create',
};
const factory = (props = {}) => {
const localVue = createLocalVue();
wrapper = mount(localVue.extend(Form), {
localVue,
propsData: props,
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
});
it('should render provided submitText', () => {
factory(requiredProps);
expect(wrapper.find('.js-ff-submit').text()).toEqual(requiredProps.submitText);
});
it('should render provided cancelPath', () => {
factory(requiredProps);
expect(wrapper.find('.js-ff-cancel').attributes('href')).toEqual(requiredProps.cancelPath);
});
describe('without provided data', () => {
beforeEach(() => {
factory(requiredProps);
});
it('should render name input text', () => {
expect(wrapper.find('#feature-flag-name').exists()).toBe(true);
});
it('should render description textarea', () => {
expect(wrapper.find('#feature-flag-description').exists()).toBe(true);
});
describe('scopes', () => {
it('should render scopes table', () => {
expect(wrapper.find('.js-scopes-table').exists()).toBe(true);
});
it('should render scopes table with a new row ', () => {
expect(wrapper.find('.js-add-new-scope').exists()).toBe(true);
});
describe('status toggle', () => {
describe('with filled text input', () => {
it('should add a new scope with the text value and the status and reset the form', () => {
wrapper.find('.js-new-scope-name').setValue('production');
wrapper.find(ToggleButton).vm.$emit('change', true);
expect(wrapper.vm.formScopes).toEqual([
{ active: true, environment_scope: 'production' },
]);
expect(wrapper.vm.newScope).toEqual('');
});
});
describe('without filled text input', () => {
it('should add a new scope with the text value empty and the status', () => {
wrapper.find(ToggleButton).vm.$emit('change', true);
expect(wrapper.vm.formScopes).toEqual([{ active: true, environment_scope: '' }]);
expect(wrapper.vm.newScope).toEqual('');
});
});
});
});
});
describe('with provided data', () => {
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environment_scope: 'production',
active: false,
id: 2,
},
],
});
});
describe('scopes', () => {
it('should be possible to remove a scope', () => {
expect(wrapper.find('.js-feature-flag-delete').exists()).toEqual(true);
});
it('renders empty row to add a new scope', () => {
expect(wrapper.find('.js-add-new-scope').exists()).toEqual(true);
});
describe('update scope', () => {
describe('on click on toggle', () => {
it('should update the scope', () => {
wrapper.find(ToggleButton).vm.$emit('change', true);
expect(wrapper.vm.formScopes).toEqual([
{ active: true, environment_scope: 'production', id: 2 },
]);
expect(wrapper.vm.newScope).toEqual('');
});
});
});
describe('deleting an existing scope', () => {
beforeEach(() => {
wrapper.find('.js-delete-scope').trigger('click');
});
it('should add `_destroy` key the clicked scope', () => {
expect(wrapper.vm.formScopes).toEqual([
{
environment_scope: 'production',
active: false,
_destroy: true,
id: 2,
},
]);
});
it('should not render deleted scopes', () => {
expect(wrapper.vm.filteredScopes).toEqual([]);
});
});
describe('deleting a new scope', () => {
it('should remove the scope from formScopes', () => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environment_scope: 'new_scope',
active: false,
},
],
});
wrapper.find('.js-delete-scope').trigger('click');
expect(wrapper.vm.formScopes).toEqual([]);
});
});
describe('with * scope', () => {
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environment_scope: '*',
active: false,
},
],
});
});
it('renders read only name', () => {
expect(wrapper.find('.js-scope-all').exists()).toEqual(true);
});
});
});
describe('on submit', () => {
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environment_scope: 'production',
active: false,
},
],
});
});
it('should emit handleSubmit with the updated data', () => {
wrapper.find('#feature-flag-name').setValue('feature_flag_2');
wrapper.find('.js-new-scope-name').setValue('review');
wrapper
.find('.js-add-new-scope')
.find(ToggleButton)
.vm.$emit('change', true);
wrapper.vm.handleSubmit();
expect(wrapper.emitted().handleSubmit[0]).toEqual([
{
name: 'feature_flag_2',
description: 'this is a feature flag',
scopes: [
{
environment_scope: 'production',
active: false,
},
{
environment_scope: 'review',
active: true,
},
],
},
]);
});
});
});
});
import Vuex from 'vuex';
import Vue from 'vue';
import { createLocalVue, mount } from '@vue/test-utils';
import Form from 'ee/feature_flags/components/form.vue';
import newModule from 'ee/feature_flags/store/modules/new';
import NewFeatureFlag from 'ee/feature_flags/components/new_feature_flag.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('New feature flag form', () => {
let wrapper;
const store = new Vuex.Store({
modules: {
new: newModule,
},
});
const factory = () => {
wrapper = mount(localVue.extend(NewFeatureFlag), {
localVue,
propsData: {
endpoint: 'feature_flags.json',
path: '/feature_flags',
},
store,
sync: false,
});
};
beforeEach(() => {
factory();
});
afterEach(() => {
wrapper.destroy();
});
describe('with error', () => {
it('should render the error', done => {
store.dispatch('new/receiveCreateFeatureFlagError', { message: ['The name is required'] });
Vue.nextTick(() => {
expect(wrapper.find('.alert').exists()).toEqual(true);
expect(wrapper.find('.alert').text()).toContain('The name is required');
done();
});
});
});
it('renders form title', () => {
expect(wrapper.find('h3').text()).toEqual('New Feature Flag');
});
it('should render feature flag form', () => {
expect(wrapper.find(Form).exists()).toEqual(true);
});
it('should render default * row', () => {
expect(wrapper.vm.scopes).toEqual([
{
environment_scope: '*',
active: true,
},
]);
expect(wrapper.find('.js-scope-all').exists()).toEqual(true);
});
});
......@@ -67,14 +67,11 @@ describe('Feature flags Edit Module actions', () => {
it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess ', done => {
mock
.onPut(mockedState.endpoint, {
params: {
operations_feature_flags: {
operations_feature_flag: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(200);
......@@ -83,7 +80,7 @@ describe('Feature flags Edit Module actions', () => {
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
scopes: [{ environment_scope: '*', active: true }],
},
mockedState,
[],
......@@ -104,14 +101,11 @@ describe('Feature flags Edit Module actions', () => {
it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagError ', done => {
mock
.onPut(`${TEST_HOST}/endpoint.json`, {
params: {
operations_feature_flags: {
operations_feature_flag: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(500, { message: [] });
......@@ -120,7 +114,7 @@ describe('Feature flags Edit Module actions', () => {
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
scopes: [{ environment_scope: '*', active: true }],
},
mockedState,
[],
......
......@@ -63,14 +63,11 @@ describe('Feature flags New Module Actions', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagSuccess ', done => {
mock
.onPost(`${TEST_HOST}/endpoint.json`, {
params: {
operations_feature_flags: {
operations_feature_flag: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(200);
......@@ -79,7 +76,7 @@ describe('Feature flags New Module Actions', () => {
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
scopes: [{ environment_scope: '*', active: true }],
},
mockedState,
[],
......@@ -100,14 +97,11 @@ describe('Feature flags New Module Actions', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagError ', done => {
mock
.onPost(`${TEST_HOST}/endpoint.json`, {
params: {
operations_feature_flags: {
operations_feature_flag: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(500, { message: [] });
......@@ -116,7 +110,7 @@ describe('Feature flags New Module Actions', () => {
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
scopes: [{ environment_scope: '*', active: true }],
},
mockedState,
[],
......
......@@ -3266,6 +3266,9 @@ msgstr ""
msgid "Edit"
msgstr ""
msgid "Edit %{name}"
msgstr ""
msgid "Edit Label"
msgstr ""
......@@ -3800,6 +3803,9 @@ msgstr ""
msgid "Feature Flags"
msgstr ""
msgid "FeatureFlags|* (All Environments)"
msgstr ""
msgid "FeatureFlags|* (All environments)"
msgstr ""
......@@ -3833,12 +3839,18 @@ msgstr ""
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
msgid "FeatureFlags|Environment Spec"
msgstr ""
msgid "FeatureFlags|Environment Specs"
msgstr ""
msgid "FeatureFlags|Feature Flag"
msgstr ""
msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
msgstr ""
msgid "FeatureFlags|Feature Flags"
msgstr ""
......@@ -3887,6 +3899,9 @@ msgstr ""
msgid "FeatureFlags|Status"
msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
msgid "FeatureFlags|There was an error fetching the feature flags."
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