Commit 9d038fd5 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch 'create-user-list-fe' into 'master'

Allow Users to Create and Rename User Lists

Closes #224229

See merge request gitlab-org/gitlab!36598
parents f0e5acf3 878f79d7
......@@ -2,13 +2,12 @@
import { createNamespacedHelpers } from 'vuex';
import { isEmpty } from 'lodash';
import {
GlAlert,
GlButton,
GlEmptyState,
GlLoadingIcon,
GlModalDirective,
GlLink,
GlAlert,
GlSprintf,
} from '@gitlab/ui';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../constants';
import FeatureFlagsTable from './feature_flags_table.vue';
......@@ -34,12 +33,11 @@ export default {
UserListsTable,
NavigationTabs,
TablePagination,
GlAlert,
GlButton,
GlEmptyState,
GlLoadingIcon,
GlButton,
GlLink,
GlAlert,
GlSprintf,
ConfigureFeatureFlagsModal,
},
directives: {
......@@ -74,10 +72,6 @@ export default {
type: String,
required: true,
},
userListsApiDocPath: {
type: String,
required: true,
},
rotateInstanceIdPath: {
type: String,
required: false,
......@@ -100,6 +94,11 @@ export default {
required: false,
default: '',
},
newUserListPath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
......@@ -267,6 +266,18 @@ export default {
>
{{ s__('FeatureFlags|Configure') }}
</gl-button>
<gl-button
v-if="newUserListPath"
:href="newUserListPath"
variant="success"
category="secondary"
class="gl-mr-3"
data-testid="ff-new-list-button"
>
{{ s__('FeatureFlags|New list') }}
</gl-button>
<gl-button
v-if="hasNewPath"
:href="newFeatureFlagPath"
......@@ -277,20 +288,6 @@ export default {
</gl-button>
</div>
</div>
<gl-alert v-if="!isUserListAlertDismissed" @dismiss="isUserListAlertDismissed = true">
<gl-sprintf
:message="
__('User Lists can only be created and modified with %{linkStart}the API%{linkEnd}')
"
>
<template #link="{ content }">
<gl-link :href="userListsApiDocPath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<gl-alert
v-for="(message, index) in alerts"
:key="index"
......
......@@ -29,7 +29,7 @@ export default () =>
canUserConfigure: this.dataset.canUserAdminFeatureFlag,
newFeatureFlagPath: this.dataset.newFeatureFlagPath,
rotateInstanceIdPath: this.dataset.rotateInstanceIdPath,
userListsApiDocPath: this.dataset.userListsApiDocPath,
newUserListPath: this.dataset.newUserListPath,
},
});
},
......
......@@ -39,6 +39,7 @@ export default {
userIdLabel: s__('UserLists|User IDs'),
userIdColumnHeader: s__('UserLists|User ID'),
errorMessage: __('Something went wrong on our end. Please try again!'),
editButtonLabel: s__('UserLists|Edit'),
},
classes: {
headerClasses: [
......@@ -72,6 +73,9 @@ export default {
hasError() {
return this.state === states.ERROR;
},
editPath() {
return this.userList?.edit_path;
},
},
mounted() {
this.fetchUserList();
......@@ -86,7 +90,7 @@ export default {
<gl-alert v-if="hasError" variant="danger" @dismiss="dismissErrorAlert">
{{ $options.translations.errorMessage }}
</gl-alert>
<gl-loading-icon v-if="isLoading" size="xl" class="mt-5" />
<gl-loading-icon v-if="isLoading" size="xl" class="gl-mt-6" />
<div v-else>
<add-user-modal @addUsers="addUserIds" />
<div :class="$options.classes.headerClasses">
......@@ -94,7 +98,10 @@ export default {
<h3>{{ name }}</h3>
<h4 class="gl-text-gray-700">{{ $options.translations.userIdLabel }}</h4>
</div>
<div class="mt-5">
<div class="gl-mt-6">
<gl-button v-if="editPath" :href="editPath" data-testid="edit-user-list" class="gl-mr-3">
{{ $options.translations.editButtonLabel }}
</gl-button>
<gl-button
v-gl-modal="$options.ADD_USER_MODAL_ID"
data-testid="add-users"
......
# frozen_string_literal: true
class Projects::FeatureFlagsUserListsController < Projects::ApplicationController
before_action :check_feature_flag!, only: [:new, :edit]
before_action :authorize_admin_feature_flags_user_lists!
before_action :user_list, only: [:edit, :show]
......@@ -16,10 +15,6 @@ class Projects::FeatureFlagsUserListsController < Projects::ApplicationControlle
private
def check_feature_flag!
not_found unless Feature.enabled?(:feature_flag_user_lists, project)
end
def user_list
@user_list = project.operations_feature_flags_user_lists.find_by_iid!(params[:iid])
end
......
......@@ -11,4 +11,4 @@
"can-user-admin-feature-flag" => can?(current_user, :admin_feature_flag, @project),
"new-feature-flag-path" => can?(current_user, :create_feature_flag, @project) ? new_project_feature_flag_path(@project): nil,
"rotate-instance-id-path" => can?(current_user, :admin_feature_flags_client, @project) ? reset_token_project_feature_flags_client_path(@project, format: :json) : nil,
"user-lists-api-doc-path" => help_page_path("api/feature_flag_user_lists.md") } }
"new-user-list-path" => can?(current_user, :admin_feature_flags_user_lists, @project) ? new_project_feature_flags_user_list_path(@project) : nil } }
---
title: Allow Users to Create and Rename User Lists
merge_request: 36598
author:
type: added
......@@ -18,6 +18,10 @@ module EE
expose :path do |list|
project_feature_flags_user_list_path(list.project, list)
end
expose :edit_path do |list|
edit_project_feature_flags_user_list_path(list.project, list)
end
end
end
end
......
......@@ -60,15 +60,6 @@ RSpec.describe Projects::FeatureFlagsUserListsController do
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns not found when the feature flag is off' do
stub_feature_flags(feature_flag_user_lists: false)
sign_in(developer)
get(:new, params: request_params)
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'GET #edit' do
......@@ -100,15 +91,6 @@ RSpec.describe Projects::FeatureFlagsUserListsController do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns not found when the feature flag is off' do
stub_feature_flags(feature_flag_user_lists: false)
list = create(:operations_feature_flag_user_list, project: project)
get(:edit, params: request_params(iid: list.iid))
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'GET #show' do
......
import { shallowMount, mount } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { GlEmptyState, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import Api from 'ee/api';
import store from 'ee/feature_flags/store';
import FeatureFlagsComponent from 'ee/feature_flags/components/feature_flags.vue';
......@@ -9,7 +9,6 @@ import UserListsTable from 'ee/feature_flags/components/user_lists_table.vue';
import ConfigureFeatureFlagsModal from 'ee/feature_flags/components/configure_feature_flags_modal.vue';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from 'ee/feature_flags/constants';
import { TEST_HOST } from 'spec/test_constants';
import { trimText } from 'helpers/text_helper';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import axios from '~/lib/utils/axios_utils';
......@@ -23,12 +22,12 @@ describe('Feature flags', () => {
featureFlagsHelpPagePath: '/help/feature-flags',
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
userListsApiDocPath: '/help/api/user_lists',
unleashApiUrl: `${TEST_HOST}/api/unleash`,
unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F',
canUserConfigure: true,
canUserRotateToken: true,
newFeatureFlagPath: 'feature-flags/new',
newUserListPath: '/user-list/new',
projectId: '8',
};
......@@ -43,6 +42,7 @@ describe('Feature flags', () => {
const configureButton = () => wrapper.find('[data-testid="ff-configure-button"]');
const newButton = () => wrapper.find('[data-testid="ff-new-button"]');
const newUserListButton = () => wrapper.find('[data-testid="ff-new-list-button"]');
beforeEach(() => {
mock = new MockAdapter(axios);
......@@ -65,30 +65,6 @@ describe('Feature flags', () => {
wrapper.destroy();
});
describe('user lists alert', () => {
let alert;
beforeEach(async () => {
factory(mockData, mount);
await wrapper.vm.$nextTick();
alert = wrapper.find(GlAlert);
});
it('should show that user lists can only be modified by the API', () => {
expect(trimText(alert.text())).toContain(
'User Lists can only be created and modified with the API',
);
});
it('should be dismissible', async () => {
alert.find('button').trigger('click');
await wrapper.vm.$nextTick();
expect(alert.exists()).toBe(false);
});
});
describe('without permissions', () => {
const propsData = {
endpoint: `${TEST_HOST}/endpoint.json`,
......@@ -102,7 +78,6 @@ describe('Feature flags', () => {
unleashApiUrl: `${TEST_HOST}/api/unleash`,
unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F',
projectId: '8',
userListsApiDocPath: '/help/api/user_lists',
};
beforeEach(done => {
......@@ -124,6 +99,10 @@ describe('Feature flags', () => {
it('does not render new feature flag button', () => {
expect(newButton().exists()).toBe(false);
});
it('does not render new user list button', () => {
expect(newUserListButton().exists()).toBe(false);
});
});
describe('loading state', () => {
......@@ -181,6 +160,11 @@ describe('Feature flags', () => {
expect(newButton().exists()).toBe(true);
});
it('renders new user list button', () => {
expect(newUserListButton().exists()).toBe(true);
expect(newUserListButton().attributes('href')).toBe('/user-list/new');
});
describe('in feature flags tab', () => {
it('renders generic title', () => {
expect(emptyState.props('title')).toEqual('Get started with feature flags');
......@@ -237,6 +221,11 @@ describe('Feature flags', () => {
expect(newButton().exists()).toBe(true);
});
it('renders new user list button', () => {
expect(newUserListButton().exists()).toBe(true);
expect(newUserListButton().attributes('href')).toBe('/user-list/new');
});
describe('pagination', () => {
it('should render pagination', () => {
expect(wrapper.find(TablePagination).exists()).toBe(true);
......@@ -316,6 +305,11 @@ describe('Feature flags', () => {
it('renders new feature flag button', () => {
expect(newButton().exists()).toBe(true);
});
it('renders new user list button', () => {
expect(newUserListButton().exists()).toBe(true);
expect(newUserListButton().attributes('href')).toBe('/user-list/new');
});
});
describe('rotate instance id', () => {
......
......@@ -99,6 +99,7 @@ export const userList = {
created_at: '2020-02-04T08:13:10.507Z',
updated_at: '2020-02-04T08:13:10.507Z',
path: '/path/to/user/list',
edit_path: '/path/to/user/list/edit',
};
export const allUsersStrategy = {
......
......@@ -77,6 +77,10 @@ describe('User List', () => {
expect(wrapper.find('[data-testid="add-users"]').text()).toBe('Add Users');
});
it('shows an edit list button', () => {
expect(wrapper.find('[data-testid="edit-user-list"]').text()).toBe('Edit');
});
it('shows a row for every id', () => {
expect(wrapper.findAll('[data-testid="user-id-row"]')).toHaveLength(userIds.length);
});
......
......@@ -59,7 +59,8 @@ RSpec.describe API::FeatureFlagsUserLists do
'updated_at' => user_list.updated_at.as_json,
'name' => 'list_a',
'user_xids' => 'user1',
'path' => project_feature_flags_user_list_path(user_list.project, user_list)
'path' => project_feature_flags_user_list_path(user_list.project, user_list),
'edit_path' => edit_project_feature_flags_user_list_path(user_list.project, user_list)
}])
end
......@@ -110,7 +111,7 @@ RSpec.describe API::FeatureFlagsUserLists do
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns the feature flag' do
it 'returns the user list' do
list = create_list(name: 'testers', user_xids: 'test1,test2')
get api("/projects/#{project.id}/feature_flags_user_lists/#{list.iid}", developer)
......@@ -124,11 +125,12 @@ RSpec.describe API::FeatureFlagsUserLists do
'project_id' => project.id,
'created_at' => list.created_at.as_json,
'updated_at' => list.updated_at.as_json,
'path' => project_feature_flags_user_list_path(list.project, list)
'path' => project_feature_flags_user_list_path(list.project, list),
'edit_path' => edit_project_feature_flags_user_list_path(list.project, list)
})
end
it 'returns the correct feature flag identified by the iid' do
it 'returns the correct user list identified by the iid' do
create_list(name: 'list_a', user_xids: 'test1')
list_b = create_list(name: 'list_b', user_xids: 'test2')
......
......@@ -10336,6 +10336,9 @@ msgstr ""
msgid "FeatureFlags|New feature flag"
msgstr ""
msgid "FeatureFlags|New list"
msgstr ""
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr ""
......@@ -26223,9 +26226,6 @@ msgstr ""
msgid "User List"
msgstr ""
msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
msgstr ""
msgid "User OAuth applications"
msgstr ""
......@@ -26295,6 +26295,9 @@ msgstr ""
msgid "UserLists|Define a set of users to be used within feature flag strategies"
msgstr ""
msgid "UserLists|Edit"
msgstr ""
msgid "UserLists|Edit %{name}"
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