diff options
Diffstat (limited to 'app/assets')
17 files changed, 186 insertions, 77 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 63b75cdb734..0c6acbf4e45 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -737,6 +737,12 @@ const Api = { return axios.get(url, { params: { page } }); }, + searchFeatureFlagUserLists(id, search) { + const url = Api.buildUrl(this.featureFlagUserLists).replace(':id', id); + + return axios.get(url, { params: { search } }); + }, + createFeatureFlagUserList(id, list) { const url = Api.buildUrl(this.featureFlagUserLists).replace(':id', id); diff --git a/app/assets/javascripts/ci_lint/components/ci_lint.vue b/app/assets/javascripts/ci_lint/components/ci_lint.vue index 2532f4b86d2..cae82ea316c 100644 --- a/app/assets/javascripts/ci_lint/components/ci_lint.vue +++ b/app/assets/javascripts/ci_lint/components/ci_lint.vue @@ -27,6 +27,7 @@ export default { data() { return { content: '', + loading: false, valid: false, errors: null, warnings: null, @@ -44,6 +45,7 @@ export default { }, methods: { async lint() { + this.loading = true; try { const { data: { @@ -62,6 +64,8 @@ export default { } catch (error) { this.apiError = error; this.isErrorDismissed = false; + } finally { + this.loading = false; } }, clear() { @@ -93,6 +97,7 @@ export default { <div class="gl-display-flex gl-align-items-center"> <gl-button class="gl-mr-4" + :loading="loading" category="primary" variant="success" data-testid="ci-lint-validate" diff --git a/app/assets/javascripts/feature_flags/components/form.vue b/app/assets/javascripts/feature_flags/components/form.vue index 3e2505b460a..36ebf893486 100644 --- a/app/assets/javascripts/feature_flags/components/form.vue +++ b/app/assets/javascripts/feature_flags/components/form.vue @@ -11,10 +11,8 @@ import { GlSprintf, GlIcon, } from '@gitlab/ui'; -import Api from '~/api'; import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue'; import { s__ } from '~/locale'; -import { deprecatedCreateFlash as flash, FLASH_TYPES } from '~/flash'; import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import ToggleButton from '~/vue_shared/components/toggle_button.vue'; import EnvironmentsDropdown from './environments_dropdown.vue'; @@ -89,7 +87,6 @@ export default { }, }, inject: { - projectId: {}, featureFlagIssuesEndpoint: { default: '', }, @@ -124,7 +121,6 @@ export default { formStrategies: cloneDeep(this.strategies), newScope: '', - userLists: [], }; }, computed: { @@ -155,17 +151,6 @@ export default { ); }, }, - mounted() { - if (this.supportsStrategies) { - Api.fetchFeatureFlagUserLists(this.projectId) - .then(({ data }) => { - this.userLists = data; - }) - .catch(() => { - flash(s__('FeatureFlags|There was an error retrieving user lists'), FLASH_TYPES.WARNING); - }); - } - }, methods: { keyFor(strategy) { if (strategy.id) { @@ -346,7 +331,6 @@ export default { :key="keyFor(strategy)" :strategy="strategy" :index="index" - :user-lists="userLists" @change="onFormStrategyChange($event, index)" @delete="deleteStrategy(strategy)" /> diff --git a/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue b/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue index ec97e8b1350..273bd47cfc1 100644 --- a/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue +++ b/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue @@ -1,11 +1,20 @@ <script> -import { GlFormSelect } from '@gitlab/ui'; +import { debounce } from 'lodash'; +import { createNamespacedHelpers } from 'vuex'; +import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui'; import { s__ } from '~/locale'; import ParameterFormGroup from './parameter_form_group.vue'; +const { mapActions, mapGetters, mapState } = createNamespacedHelpers('userLists'); + +const { fetchUserLists, setFilter } = mapActions(['fetchUserLists', 'setFilter']); + export default { components: { - GlFormSelect, + GlDropdown, + GlDropdownItem, + GlLoadingIcon, + GlSearchBoxByType, ParameterFormGroup, }, props: { @@ -13,34 +22,40 @@ export default { required: true, type: Object, }, - userLists: { - required: false, - type: Array, - default: () => [], - }, }, translations: { - rolloutUserListLabel: s__('FeatureFlag|List'), + rolloutUserListLabel: s__('FeatureFlag|User List'), rolloutUserListDescription: s__('FeatureFlag|Select a user list'), rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'), + defaultDropdownText: s__('FeatureFlags|Select a user list'), }, computed: { - userListOptions() { - return this.userLists.map(({ name, id }) => ({ value: id, text: name })); - }, - hasUserLists() { - return this.userListOptions.length > 0; - }, + ...mapGetters(['hasUserLists', 'isLoading', 'hasError', 'userListOptions']), + ...mapState(['filter', 'userLists']), userListId() { - return this.strategy?.userListId ?? ''; + return this.strategy?.userList?.id ?? ''; }, + dropdownText() { + return this.strategy?.userList?.name ?? this.$options.defaultDropdownText; + }, + }, + mounted() { + fetchUserLists.apply(this); }, methods: { + setFilter: debounce(setFilter, 250), + fetchUserLists: debounce(fetchUserLists, 250), onUserListChange(list) { this.$emit('change', { - userListId: list, + userList: list, }); }, + isSelectedUserList({ id }) { + return id === this.userListId; + }, + setFocus() { + this.$refs.searchBox.focusInput(); + }, }, }; </script> @@ -52,12 +67,26 @@ export default { :description="hasUserLists ? $options.translations.rolloutUserListDescription : ''" > <template #default="{ inputId }"> - <gl-form-select - :id="inputId" - :value="userListId" - :options="userListOptions" - @change="onUserListChange" - /> + <gl-dropdown :id="inputId" :text="dropdownText" @shown="setFocus"> + <gl-search-box-by-type + ref="searchBox" + class="gl-m-3" + :value="filter" + @input="setFilter" + @focus="fetchUserLists" + @keyup="fetchUserLists" + /> + <gl-loading-icon v-if="isLoading" /> + <gl-dropdown-item + v-for="list in userLists" + :key="list.id" + :is-checked="isSelectedUserList(list)" + is-check-item + @click="onUserListChange(list)" + > + {{ list.name }} + </gl-dropdown-item> + </gl-dropdown> </template> </parameter-form-group> </template> diff --git a/app/assets/javascripts/feature_flags/edit.js b/app/assets/javascripts/feature_flags/edit.js index b4d2111acf3..05a9bbce654 100644 --- a/app/assets/javascripts/feature_flags/edit.js +++ b/app/assets/javascripts/feature_flags/edit.js @@ -22,7 +22,7 @@ export default () => { } = el.dataset; return new Vue({ - store: createStore({ endpoint, path: featureFlagsPath }), + store: createStore({ endpoint, projectId, path: featureFlagsPath }), el, provide: { environmentsScopeDocsPath, diff --git a/app/assets/javascripts/feature_flags/new.js b/app/assets/javascripts/feature_flags/new.js index a1efbd87ec4..8e18213cc03 100644 --- a/app/assets/javascripts/feature_flags/new.js +++ b/app/assets/javascripts/feature_flags/new.js @@ -22,7 +22,7 @@ export default () => { return new Vue({ el, - store: createStore({ endpoint, path: featureFlagsPath }), + store: createStore({ endpoint, projectId, path: featureFlagsPath }), provide: { environmentsScopeDocsPath, strategyTypeDocsPagePath, diff --git a/app/assets/javascripts/feature_flags/store/edit/index.js b/app/assets/javascripts/feature_flags/store/edit/index.js index f737e0517fc..81edc791924 100644 --- a/app/assets/javascripts/feature_flags/store/edit/index.js +++ b/app/assets/javascripts/feature_flags/store/edit/index.js @@ -1,4 +1,5 @@ import Vuex from 'vuex'; +import userLists from '../gitlab_user_list'; import state from './state'; import * as actions from './actions'; import mutations from './mutations'; @@ -8,4 +9,7 @@ export default data => actions, mutations, state: state(data), + modules: { + userLists: userLists(data), + }, }); diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/actions.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/actions.js new file mode 100644 index 00000000000..d4587713fed --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/actions.js @@ -0,0 +1,17 @@ +import Api from '~/api'; +import * as types from './mutation_types'; + +const getErrorMessages = error => [].concat(error?.response?.data?.message ?? error.message); + +export const fetchUserLists = ({ commit, state: { filter, projectId } }) => { + commit(types.FETCH_USER_LISTS); + + return Api.searchFeatureFlagUserLists(projectId, filter) + .then(({ data }) => commit(types.RECEIVE_USER_LISTS_SUCCESS, data)) + .catch(error => commit(types.RECEIVE_USER_LISTS_ERROR, getErrorMessages(error))); +}; + +export const setFilter = ({ commit, dispatch }, filter) => { + commit(types.SET_FILTER, filter); + return dispatch('fetchUserLists'); +}; diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/getters.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/getters.js new file mode 100644 index 00000000000..164b0980120 --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/getters.js @@ -0,0 +1,11 @@ +import statuses from './status'; + +export const userListOptions = ({ userLists }) => + userLists.map(({ name, id }) => ({ value: id, text: name })); + +export const hasUserLists = ({ userLists, status }) => + [statuses.START, statuses.LOADING].indexOf(status) > -1 || userLists.length > 0; + +export const isLoading = ({ status }) => status === statuses.LOADING; + +export const hasError = ({ status }) => status === statuses.ERROR; diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/index.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/index.js new file mode 100644 index 00000000000..d25b574981f --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/index.js @@ -0,0 +1,12 @@ +import state from './state'; +import mutations from './mutations'; +import * as actions from './actions'; +import * as getters from './getters'; + +export default data => ({ + state: state(data), + actions, + getters, + mutations, + namespaced: true, +}); diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/mutation_types.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/mutation_types.js new file mode 100644 index 00000000000..0fe12f06785 --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/mutation_types.js @@ -0,0 +1,5 @@ +export const FETCH_USER_LISTS = 'FETCH_USER_LISTS'; +export const RECEIVE_USER_LISTS_SUCCESS = 'RECEIVE_USER_LISTS_SUCCESS'; +export const RECEIVE_USER_LISTS_ERROR = 'RECEIVE_USER_LISTS_ERROR'; + +export const SET_FILTER = 'SET_FILTER'; diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/mutations.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/mutations.js new file mode 100644 index 00000000000..bd7c6f68009 --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/mutations.js @@ -0,0 +1,19 @@ +import statuses from './status'; +import * as types from './mutation_types'; + +export default { + [types.FETCH_USER_LISTS](state) { + state.status = statuses.LOADING; + }, + [types.RECEIVE_USER_LISTS_SUCCESS](state, lists) { + state.userLists = lists; + state.status = statuses.IDLE; + }, + [types.RECEIVE_USER_LISTS_ERROR](state, error) { + state.error = error; + state.status = statuses.ERROR; + }, + [types.SET_FILTER](state, filter) { + state.filter = filter; + }, +}; diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/state.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/state.js new file mode 100644 index 00000000000..2664ec794fc --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/state.js @@ -0,0 +1,9 @@ +import statuses from './status'; + +export default ({ projectId }) => ({ + projectId, + userLists: [], + filter: '', + status: statuses.START, + error: '', +}); diff --git a/app/assets/javascripts/feature_flags/store/gitlab_user_list/status.js b/app/assets/javascripts/feature_flags/store/gitlab_user_list/status.js new file mode 100644 index 00000000000..67f153eb58e --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/gitlab_user_list/status.js @@ -0,0 +1,6 @@ +export default { + START: 'START', + LOADING: 'LOADING', + IDLE: 'IDLE', + ERROR: 'ERROR', +}; diff --git a/app/assets/javascripts/feature_flags/store/helpers.js b/app/assets/javascripts/feature_flags/store/helpers.js index db6da815abf..d42e5c504db 100644 --- a/app/assets/javascripts/feature_flags/store/helpers.js +++ b/app/assets/javascripts/feature_flags/store/helpers.js @@ -174,7 +174,7 @@ export const mapStrategiesToViewModel = strategiesFromRails => id: s.id, name: s.name, parameters: mapStrategiesParametersToViewModel(s.parameters), - userListId: s.user_list?.id, + userList: s.user_list, // eslint-disable-next-line no-underscore-dangle shouldBeDestroyed: Boolean(s._destroy), scopes: mapStrategyScopesToView(s.scopes), @@ -197,7 +197,7 @@ const mapStrategyToRails = strategy => { }; if (strategy.name === ROLLOUT_STRATEGY_GITLAB_USER_LIST) { - mappedStrategy.user_list_id = strategy.userListId; + mappedStrategy.user_list_id = strategy.userList.id; } return mappedStrategy; }; diff --git a/app/assets/javascripts/feature_flags/store/new/index.js b/app/assets/javascripts/feature_flags/store/new/index.js index f737e0517fc..81edc791924 100644 --- a/app/assets/javascripts/feature_flags/store/new/index.js +++ b/app/assets/javascripts/feature_flags/store/new/index.js @@ -1,4 +1,5 @@ import Vuex from 'vuex'; +import userLists from '../gitlab_user_list'; import state from './state'; import * as actions from './actions'; import mutations from './mutations'; @@ -8,4 +9,7 @@ export default data => actions, mutations, state: state(data), + modules: { + userLists: userLists(data), + }, }); diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js index 6f5aef54cef..7d4df25816b 100644 --- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js +++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js @@ -96,43 +96,41 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { IssuableTokenKeys.tokenKeysWithAlternative.splice(tokenPosition, 0, ...[approvedBy.token]); IssuableTokenKeys.conditions.push(...approvedBy.condition); - if (gon?.features?.deploymentFilters) { - const environmentToken = { - formattedKey: __('Environment'), - key: 'environment', - type: 'string', - param: '', - symbol: '', - icon: 'cloud-gear', - tag: 'environment', - }; + const environmentToken = { + formattedKey: __('Environment'), + key: 'environment', + type: 'string', + param: '', + symbol: '', + icon: 'cloud-gear', + tag: 'environment', + }; - const deployedBeforeToken = { - formattedKey: __('Deployed-before'), - key: 'deployed-before', - type: 'string', - param: '', - symbol: '', - icon: 'clock', - tag: 'deployed_before', - }; + const deployedBeforeToken = { + formattedKey: __('Deployed-before'), + key: 'deployed-before', + type: 'string', + param: '', + symbol: '', + icon: 'clock', + tag: 'deployed_before', + }; - const deployedAfterToken = { - formattedKey: __('Deployed-after'), - key: 'deployed-after', - type: 'string', - param: '', - symbol: '', - icon: 'clock', - tag: 'deployed_after', - }; + const deployedAfterToken = { + formattedKey: __('Deployed-after'), + key: 'deployed-after', + type: 'string', + param: '', + symbol: '', + icon: 'clock', + tag: 'deployed_after', + }; - IssuableTokenKeys.tokenKeys.push(environmentToken, deployedBeforeToken, deployedAfterToken); + IssuableTokenKeys.tokenKeys.push(environmentToken, deployedBeforeToken, deployedAfterToken); - IssuableTokenKeys.tokenKeysWithAlternative.push( - environmentToken, - deployedBeforeToken, - deployedAfterToken, - ); - } + IssuableTokenKeys.tokenKeysWithAlternative.push( + environmentToken, + deployedBeforeToken, + deployedAfterToken, + ); }; |