diff options
Diffstat (limited to 'app/assets/javascripts/user_lists')
23 files changed, 653 insertions, 0 deletions
diff --git a/app/assets/javascripts/user_lists/components/add_user_modal.vue b/app/assets/javascripts/user_lists/components/add_user_modal.vue new file mode 100644 index 00000000000..a8dde1f681e --- /dev/null +++ b/app/assets/javascripts/user_lists/components/add_user_modal.vue @@ -0,0 +1,72 @@ +<script> +import { GlModal, GlFormGroup, GlFormTextarea } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { ADD_USER_MODAL_ID } from '../constants/show'; + +export default { + components: { + GlFormGroup, + GlFormTextarea, + GlModal, + }, + props: { + visible: { + type: Boolean, + required: false, + default: false, + }, + }, + modalOptions: { + actionPrimary: { + text: s__('UserLists|Add'), + attributes: [{ 'data-testid': 'confirm-add-user-ids' }], + }, + actionCancel: { + text: s__('UserLists|Cancel'), + attributes: [{ 'data-testid': 'cancel-add-user-ids' }], + }, + modalId: ADD_USER_MODAL_ID, + static: true, + }, + translations: { + title: s__('UserLists|Add users'), + description: s__( + 'UserLists|Enter a comma separated list of user IDs. These IDs should be the users of the system in which the feature flag is set, not GitLab IDs', + ), + userIdsLabel: s__('UserLists|User IDs'), + }, + data() { + return { + userIds: '', + }; + }, + methods: { + submitUsers() { + this.$emit('addUsers', this.userIds); + this.clearInput(); + }, + clearInput() { + this.userIds = ''; + }, + }, +}; +</script> +<template> + <gl-modal + v-bind="$options.modalOptions" + :visible="visible" + data-testid="add-users-modal" + @primary="submitUsers" + @canceled="clearInput" + > + <template #modal-title> + {{ $options.translations.title }} + </template> + <template #default> + <p data-testid="add-userids-description">{{ $options.translations.description }}</p> + <gl-form-group label-for="add-user-ids" :label="$options.translations.userIdsLabel"> + <gl-form-textarea id="add-user-ids" v-model="userIds" /> + </gl-form-group> + </template> + </gl-modal> +</template> diff --git a/app/assets/javascripts/user_lists/components/edit_user_list.vue b/app/assets/javascripts/user_lists/components/edit_user_list.vue new file mode 100644 index 00000000000..d56c3d61027 --- /dev/null +++ b/app/assets/javascripts/user_lists/components/edit_user_list.vue @@ -0,0 +1,74 @@ +<script> +import { mapActions, mapState } from 'vuex'; +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; +import statuses from '../constants/edit'; +import UserListForm from './user_list_form.vue'; + +export default { + components: { + GlAlert, + GlLoadingIcon, + UserListForm, + }, + inject: ['userListsDocsPath'], + translations: { + saveButtonLabel: s__('UserLists|Save'), + }, + computed: { + ...mapState(['userList', 'status', 'errorMessage']), + title() { + return sprintf(s__('UserLists|Edit %{name}'), { name: this.userList?.name }); + }, + isLoading() { + return this.status === statuses.LOADING; + }, + isError() { + return this.status === statuses.ERROR; + }, + hasUserList() { + return Boolean(this.userList); + }, + }, + mounted() { + this.fetchUserList(); + }, + methods: { + ...mapActions(['fetchUserList', 'updateUserList', 'dismissErrorAlert']), + }, +}; +</script> +<template> + <div> + <gl-alert + v-if="isError" + :dismissible="hasUserList" + variant="danger" + @dismiss="dismissErrorAlert" + > + <ul class="gl-mb-0"> + <li v-for="(message, index) in errorMessage" :key="index"> + {{ message }} + </li> + </ul> + </gl-alert> + + <gl-loading-icon v-if="isLoading" size="xl" /> + + <template v-else-if="hasUserList"> + <h3 + data-testid="user-list-title" + class="gl-font-weight-bold gl-pb-5 gl-border-b-solid gl-border-gray-100 gl-border-1" + > + {{ title }} + </h3> + <user-list-form + :cancel-path="userList.path" + :save-button-label="$options.translations.saveButtonLabel" + :user-lists-docs-path="userListsDocsPath" + :user-list="userList" + @submit="updateUserList" + /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/user_lists/components/new_user_list.vue b/app/assets/javascripts/user_lists/components/new_user_list.vue new file mode 100644 index 00000000000..522e077fb25 --- /dev/null +++ b/app/assets/javascripts/user_lists/components/new_user_list.vue @@ -0,0 +1,50 @@ +<script> +import { mapActions, mapState } from 'vuex'; +import { GlAlert } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import UserListForm from './user_list_form.vue'; + +export default { + components: { + GlAlert, + UserListForm, + }, + inject: ['userListsDocsPath', 'featureFlagsPath'], + translations: { + pageTitle: s__('UserLists|New list'), + createButtonLabel: s__('UserLists|Create'), + }, + computed: { + ...mapState(['userList', 'errorMessage']), + isError() { + return Array.isArray(this.errorMessage) && this.errorMessage.length > 0; + }, + }, + methods: { + ...mapActions(['createUserList', 'dismissErrorAlert']), + }, +}; +</script> +<template> + <div> + <gl-alert v-if="isError" variant="danger" @dismiss="dismissErrorAlert"> + <ul class="gl-mb-0"> + <li v-for="(message, index) in errorMessage" :key="index"> + {{ message }} + </li> + </ul> + </gl-alert> + + <h3 class="gl-font-weight-bold gl-pb-5 gl-border-b-solid gl-border-gray-100 gl-border-1"> + {{ $options.translations.pageTitle }} + </h3> + + <user-list-form + :cancel-path="featureFlagsPath" + :save-button-label="$options.translations.createButtonLabel" + :user-lists-docs-path="userListsDocsPath" + :user-list="userList" + @submit="createUserList" + /> + </div> +</template> diff --git a/app/assets/javascripts/user_lists/components/user_list.vue b/app/assets/javascripts/user_lists/components/user_list.vue new file mode 100644 index 00000000000..0e2b72c1423 --- /dev/null +++ b/app/assets/javascripts/user_lists/components/user_list.vue @@ -0,0 +1,142 @@ +<script> +import { mapActions, mapState } from 'vuex'; +import { + GlAlert, + GlButton, + GlEmptyState, + GlLoadingIcon, + GlModalDirective as GlModal, +} from '@gitlab/ui'; +import { s__, __ } from '~/locale'; +import { states, ADD_USER_MODAL_ID } from '../constants/show'; +import AddUserModal from './add_user_modal.vue'; + +const commonTableClasses = ['gl-py-5', 'gl-border-b-1', 'gl-border-b-solid', 'gl-border-gray-100']; + +export default { + components: { + GlAlert, + GlButton, + GlEmptyState, + GlLoadingIcon, + AddUserModal, + }, + directives: { + GlModal, + }, + props: { + emptyStatePath: { + required: true, + type: String, + }, + }, + translations: { + addUserButtonLabel: s__('UserLists|Add Users'), + emptyStateTitle: s__('UserLists|There are no users'), + emptyStateDescription: s__( + 'UserLists|Define a set of users to be used within feature flag strategies', + ), + 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: [ + 'gl-display-flex', + 'gl-justify-content-space-between', + 'gl-pb-5', + 'gl-border-b-1', + 'gl-border-b-solid', + 'gl-border-gray-100', + ].join(' '), + tableHeaderClasses: commonTableClasses.join(' '), + tableRowClasses: [ + ...commonTableClasses, + 'gl-display-flex', + 'gl-justify-content-space-between', + 'gl-align-items-center', + ].join(' '), + }, + ADD_USER_MODAL_ID, + computed: { + ...mapState(['userList', 'userIds', 'state']), + name() { + return this.userList?.name ?? ''; + }, + hasUserIds() { + return this.userIds.length > 0; + }, + isLoading() { + return this.state === states.LOADING; + }, + hasError() { + return this.state === states.ERROR; + }, + editPath() { + return this.userList?.edit_path; + }, + }, + mounted() { + this.fetchUserList(); + }, + methods: { + ...mapActions(['fetchUserList', 'dismissErrorAlert', 'removeUserId', 'addUserIds']), + }, +}; +</script> +<template> + <div> + <gl-alert v-if="hasError" variant="danger" @dismiss="dismissErrorAlert"> + {{ $options.translations.errorMessage }} + </gl-alert> + <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"> + <div> + <h3>{{ name }}</h3> + <h4 class="gl-text-gray-500">{{ $options.translations.userIdLabel }}</h4> + </div> + <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" + variant="success" + > + {{ $options.translations.addUserButtonLabel }} + </gl-button> + </div> + </div> + <div v-if="hasUserIds"> + <div :class="$options.classes.tableHeaderClasses"> + {{ $options.translations.userIdColumnHeader }} + </div> + <div + v-for="id in userIds" + :key="id" + data-testid="user-id-row" + :class="$options.classes.tableRowClasses" + > + <span data-testid="user-id">{{ id }}</span> + <gl-button + category="secondary" + variant="danger" + icon="remove" + data-testid="delete-user-id" + @click="removeUserId(id)" + /> + </div> + </div> + <gl-empty-state + v-else + :title="$options.translations.emptyStateTitle" + :description="$options.translations.emptyStateDescription" + :svg-path="emptyStatePath" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/user_lists/components/user_list_form.vue b/app/assets/javascripts/user_lists/components/user_list_form.vue new file mode 100644 index 00000000000..657acb51fee --- /dev/null +++ b/app/assets/javascripts/user_lists/components/user_list_form.vue @@ -0,0 +1,97 @@ +<script> +import { GlButton, GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + components: { + GlButton, + GlFormGroup, + GlFormInput, + GlLink, + GlSprintf, + }, + props: { + cancelPath: { + type: String, + required: true, + }, + saveButtonLabel: { + type: String, + required: true, + }, + userListsDocsPath: { + type: String, + required: true, + }, + userList: { + type: Object, + required: true, + }, + }, + classes: { + actionContainer: [ + 'gl-py-5', + 'gl-display-flex', + 'gl-justify-content-space-between', + 'gl-px-4', + 'gl-border-t-solid', + 'gl-border-gray-100', + 'gl-border-1', + 'gl-bg-gray-10', + ], + }, + translations: { + formLabel: s__('UserLists|Feature flag list'), + formSubtitle: s__( + 'UserLists|Lists allow you to define a set of users to be used with feature flags. %{linkStart}Read more about feature flag lists.%{linkEnd}', + ), + nameLabel: s__('UserLists|Name'), + cancelButtonLabel: s__('UserLists|Cancel'), + }, + data() { + return { + name: this.userList.name, + }; + }, + methods: { + submit() { + this.$emit('submit', { ...this.userList, name: this.name }); + }, + }, +}; +</script> +<template> + <div> + <div class="gl-display-flex gl-mt-7"> + <div class="gl-flex-basis-0 gl-mr-7"> + <h4 class="gl-min-width-fit-content gl-white-space-nowrap"> + {{ $options.translations.formLabel }} + </h4> + <gl-sprintf :message="$options.translations.formSubtitle" class="gl-text-gray-500"> + <template #link="{ content }"> + <gl-link :href="userListsDocsPath" data-testid="user-list-docs-link"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </div> + <div class="gl-flex-fill-1 gl-ml-7"> + <gl-form-group + label-for="user-list-name" + :label="$options.translations.nameLabel" + class="gl-mb-7" + > + <gl-form-input id="user-list-name" v-model="name" data-testid="user-list-name" required /> + </gl-form-group> + <div :class="$options.classes.actionContainer"> + <gl-button variant="success" data-testid="save-user-list" @click="submit"> + {{ saveButtonLabel }} + </gl-button> + <gl-button :href="cancelPath" data-testid="user-list-cancel"> + {{ $options.translations.cancelButtonLabel }} + </gl-button> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/user_lists/constants/edit.js b/app/assets/javascripts/user_lists/constants/edit.js new file mode 100644 index 00000000000..33378f0d39f --- /dev/null +++ b/app/assets/javascripts/user_lists/constants/edit.js @@ -0,0 +1,6 @@ +export default Object.freeze({ + LOADING: 'LOADING', + SUCCESS: 'SUCCESS', + ERROR: 'ERROR', + UNSYNCED: 'UNSYNCED', +}); diff --git a/app/assets/javascripts/user_lists/constants/show.js b/app/assets/javascripts/user_lists/constants/show.js new file mode 100644 index 00000000000..045375d5900 --- /dev/null +++ b/app/assets/javascripts/user_lists/constants/show.js @@ -0,0 +1,8 @@ +export const states = Object.freeze({ + LOADING: 'LOADING', + SUCCESS: 'SUCCESS', + ERROR: 'ERROR', + ERROR_DISMISSED: 'ERROR_DISMISSED', +}); + +export const ADD_USER_MODAL_ID = 'add-userids-modal'; diff --git a/app/assets/javascripts/user_lists/store/edit/actions.js b/app/assets/javascripts/user_lists/store/edit/actions.js new file mode 100644 index 00000000000..8f0a2bafec7 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/edit/actions.js @@ -0,0 +1,22 @@ +import Api from '~/api'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { getErrorMessages } from '../utils'; +import * as types from './mutation_types'; + +export const fetchUserList = ({ commit, state }) => { + commit(types.REQUEST_USER_LIST); + return Api.fetchFeatureFlagUserList(state.projectId, state.userListIid) + .then(({ data }) => commit(types.RECEIVE_USER_LIST_SUCCESS, data)) + .catch(response => commit(types.RECEIVE_USER_LIST_ERROR, getErrorMessages(response))); +}; + +export const dismissErrorAlert = ({ commit }) => commit(types.DISMISS_ERROR_ALERT); + +export const updateUserList = ({ commit, state }, userList) => { + return Api.updateFeatureFlagUserList(state.projectId, { + iid: userList.iid, + name: userList.name, + }) + .then(({ data }) => redirectTo(data.path)) + .catch(response => commit(types.RECEIVE_USER_LIST_ERROR, getErrorMessages(response))); +}; diff --git a/app/assets/javascripts/user_lists/store/edit/index.js b/app/assets/javascripts/user_lists/store/edit/index.js new file mode 100644 index 00000000000..b30b0b04b9e --- /dev/null +++ b/app/assets/javascripts/user_lists/store/edit/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import createState from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +export default initialState => + new Vuex.Store({ + actions, + mutations, + state: createState(initialState), + }); diff --git a/app/assets/javascripts/user_lists/store/edit/mutation_types.js b/app/assets/javascripts/user_lists/store/edit/mutation_types.js new file mode 100644 index 00000000000..8b572e36839 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/edit/mutation_types.js @@ -0,0 +1,5 @@ +export const REQUEST_USER_LIST = 'REQUEST_USER_LIST'; +export const RECEIVE_USER_LIST_SUCCESS = 'RECEIVE_USER_LIST_SUCCESS'; +export const RECEIVE_USER_LIST_ERROR = 'RECEIVE_USER_LIST_ERROR'; + +export const DISMISS_ERROR_ALERT = 'DISMISS_ERROR_ALERT'; diff --git a/app/assets/javascripts/user_lists/store/edit/mutations.js b/app/assets/javascripts/user_lists/store/edit/mutations.js new file mode 100644 index 00000000000..8a202885069 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/edit/mutations.js @@ -0,0 +1,19 @@ +import statuses from '../../constants/edit'; +import * as types from './mutation_types'; + +export default { + [types.REQUEST_USER_LIST](state) { + state.status = statuses.LOADING; + }, + [types.RECEIVE_USER_LIST_SUCCESS](state, userList) { + state.status = statuses.SUCCESS; + state.userList = userList; + }, + [types.RECEIVE_USER_LIST_ERROR](state, error) { + state.status = statuses.ERROR; + state.errorMessage = error; + }, + [types.DISMISS_ERROR_ALERT](state) { + state.status = statuses.UNSYNCED; + }, +}; diff --git a/app/assets/javascripts/user_lists/store/edit/state.js b/app/assets/javascripts/user_lists/store/edit/state.js new file mode 100644 index 00000000000..66fbe3c2ba9 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/edit/state.js @@ -0,0 +1,9 @@ +import statuses from '../../constants/edit'; + +export default ({ projectId = '', userListIid = '' }) => ({ + status: statuses.LOADING, + projectId, + userListIid, + userList: null, + errorMessage: [], +}); diff --git a/app/assets/javascripts/user_lists/store/new/actions.js b/app/assets/javascripts/user_lists/store/new/actions.js new file mode 100644 index 00000000000..185508bcfbc --- /dev/null +++ b/app/assets/javascripts/user_lists/store/new/actions.js @@ -0,0 +1,15 @@ +import Api from '~/api'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { getErrorMessages } from '../utils'; +import * as types from './mutation_types'; + +export const dismissErrorAlert = ({ commit }) => commit(types.DISMISS_ERROR_ALERT); + +export const createUserList = ({ commit, state }, userList) => { + return Api.createFeatureFlagUserList(state.projectId, { + ...state.userList, + ...userList, + }) + .then(({ data }) => redirectTo(data.path)) + .catch(response => commit(types.RECEIVE_CREATE_USER_LIST_ERROR, getErrorMessages(response))); +}; diff --git a/app/assets/javascripts/user_lists/store/new/index.js b/app/assets/javascripts/user_lists/store/new/index.js new file mode 100644 index 00000000000..b30b0b04b9e --- /dev/null +++ b/app/assets/javascripts/user_lists/store/new/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import createState from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +export default initialState => + new Vuex.Store({ + actions, + mutations, + state: createState(initialState), + }); diff --git a/app/assets/javascripts/user_lists/store/new/mutation_types.js b/app/assets/javascripts/user_lists/store/new/mutation_types.js new file mode 100644 index 00000000000..9a5ce6e99f5 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/new/mutation_types.js @@ -0,0 +1,3 @@ +export const RECEIVE_CREATE_USER_LIST_ERROR = 'RECEIVE_CREATE_USER_LIST_ERROR'; + +export const DISMISS_ERROR_ALERT = 'DISMISS_ERROR_ALERT'; diff --git a/app/assets/javascripts/user_lists/store/new/mutations.js b/app/assets/javascripts/user_lists/store/new/mutations.js new file mode 100644 index 00000000000..d7c1276bd72 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/new/mutations.js @@ -0,0 +1,10 @@ +import * as types from './mutation_types'; + +export default { + [types.RECEIVE_CREATE_USER_LIST_ERROR](state, error) { + state.errorMessage = error; + }, + [types.DISMISS_ERROR_ALERT](state) { + state.errorMessage = ''; + }, +}; diff --git a/app/assets/javascripts/user_lists/store/new/state.js b/app/assets/javascripts/user_lists/store/new/state.js new file mode 100644 index 00000000000..0fa73b4ffc1 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/new/state.js @@ -0,0 +1,5 @@ +export default ({ projectId = '' }) => ({ + projectId, + userList: { name: '', user_xids: '' }, + errorMessage: [], +}); diff --git a/app/assets/javascripts/user_lists/store/show/actions.js b/app/assets/javascripts/user_lists/store/show/actions.js new file mode 100644 index 00000000000..15b971aa5e8 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/show/actions.js @@ -0,0 +1,32 @@ +import Api from '~/api'; +import { stringifyUserIds } from '../utils'; +import * as types from './mutation_types'; + +export const fetchUserList = ({ commit, state }) => { + commit(types.REQUEST_USER_LIST); + return Api.fetchFeatureFlagUserList(state.projectId, state.userListIid) + .then(response => commit(types.RECEIVE_USER_LIST_SUCCESS, response.data)) + .catch(() => commit(types.RECEIVE_USER_LIST_ERROR)); +}; + +export const dismissErrorAlert = ({ commit }) => commit(types.DISMISS_ERROR_ALERT); +export const addUserIds = ({ dispatch, commit }, userIds) => { + commit(types.ADD_USER_IDS, userIds); + return dispatch('updateUserList'); +}; + +export const removeUserId = ({ commit, dispatch }, userId) => { + commit(types.REMOVE_USER_ID, userId); + return dispatch('updateUserList'); +}; + +export const updateUserList = ({ commit, state }) => { + commit(types.REQUEST_USER_LIST); + + return Api.updateFeatureFlagUserList(state.projectId, { + ...state.userList, + user_xids: stringifyUserIds(state.userIds), + }) + .then(response => commit(types.RECEIVE_USER_LIST_SUCCESS, response.data)) + .catch(() => commit(types.RECEIVE_USER_LIST_ERROR)); +}; diff --git a/app/assets/javascripts/user_lists/store/show/index.js b/app/assets/javascripts/user_lists/store/show/index.js new file mode 100644 index 00000000000..b30b0b04b9e --- /dev/null +++ b/app/assets/javascripts/user_lists/store/show/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import createState from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +export default initialState => + new Vuex.Store({ + actions, + mutations, + state: createState(initialState), + }); diff --git a/app/assets/javascripts/user_lists/store/show/mutation_types.js b/app/assets/javascripts/user_lists/store/show/mutation_types.js new file mode 100644 index 00000000000..fb967f06beb --- /dev/null +++ b/app/assets/javascripts/user_lists/store/show/mutation_types.js @@ -0,0 +1,8 @@ +export const REQUEST_USER_LIST = 'REQUEST_USER_LIST'; +export const RECEIVE_USER_LIST_SUCCESS = 'RECEIVE_USER_LIST_SUCCESS'; +export const RECEIVE_USER_LIST_ERROR = 'RECEIVE_USER_LIST_ERROR'; + +export const DISMISS_ERROR_ALERT = 'DISMISS_ERROR_ALERT'; + +export const ADD_USER_IDS = 'ADD_USER_IDS'; +export const REMOVE_USER_ID = 'REMOVE_USER_ID'; diff --git a/app/assets/javascripts/user_lists/store/show/mutations.js b/app/assets/javascripts/user_lists/store/show/mutations.js new file mode 100644 index 00000000000..c3e766465a7 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/show/mutations.js @@ -0,0 +1,29 @@ +import { states } from '../../constants/show'; +import * as types from './mutation_types'; +import { parseUserIds } from '../utils'; + +export default { + [types.REQUEST_USER_LIST](state) { + state.state = states.LOADING; + }, + [types.RECEIVE_USER_LIST_SUCCESS](state, userList) { + state.state = states.SUCCESS; + state.userIds = userList.user_xids?.length > 0 ? parseUserIds(userList.user_xids) : []; + state.userList = userList; + }, + [types.RECEIVE_USER_LIST_ERROR](state) { + state.state = states.ERROR; + }, + [types.DISMISS_ERROR_ALERT](state) { + state.state = states.ERROR_DISMISSED; + }, + [types.ADD_USER_IDS](state, ids) { + state.userIds = [ + ...state.userIds, + ...parseUserIds(ids).filter(id => id && !state.userIds.includes(id)), + ]; + }, + [types.REMOVE_USER_ID](state, id) { + state.userIds = state.userIds.filter(uid => uid !== id); + }, +}; diff --git a/app/assets/javascripts/user_lists/store/show/state.js b/app/assets/javascripts/user_lists/store/show/state.js new file mode 100644 index 00000000000..a5780893ccb --- /dev/null +++ b/app/assets/javascripts/user_lists/store/show/state.js @@ -0,0 +1,9 @@ +import { states } from '../../constants/show'; + +export default ({ projectId = '', userListIid = '' }) => ({ + state: states.LOADING, + projectId, + userListIid, + userIds: [], + userList: null, +}); diff --git a/app/assets/javascripts/user_lists/store/utils.js b/app/assets/javascripts/user_lists/store/utils.js new file mode 100644 index 00000000000..f4e46947759 --- /dev/null +++ b/app/assets/javascripts/user_lists/store/utils.js @@ -0,0 +1,5 @@ +export const parseUserIds = userIds => userIds.split(/\s*,\s*/g); + +export const stringifyUserIds = userIds => userIds.join(','); + +export const getErrorMessages = error => [].concat(error?.response?.data?.message ?? error.message); |