summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/group_select/group_select.vue')
-rw-r--r--app/assets/javascripts/vue_shared/components/group_select/group_select.vue91
1 files changed, 65 insertions, 26 deletions
diff --git a/app/assets/javascripts/vue_shared/components/group_select/group_select.vue b/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
index 5db723e1e5a..d295052e2ce 100644
--- a/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
+++ b/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
@@ -1,26 +1,35 @@
<script>
import { debounce } from 'lodash';
-import { GlCollapsibleListbox } from '@gitlab/ui';
+import { GlFormGroup, GlAlert, GlCollapsibleListbox } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import axios from '~/lib/utils/axios_utils';
+import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import Api from '~/api';
import { __ } from '~/locale';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import { createAlert } from '~/flash';
import { groupsPath } from './utils';
import {
TOGGLE_TEXT,
+ RESET_LABEL,
FETCH_GROUPS_ERROR,
FETCH_GROUP_ERROR,
QUERY_TOO_SHORT_MESSAGE,
} from './constants';
const MINIMUM_QUERY_LENGTH = 3;
+const GROUPS_PER_PAGE = 20;
export default {
components: {
+ GlFormGroup,
+ GlAlert,
GlCollapsibleListbox,
},
props: {
+ label: {
+ type: String,
+ required: true,
+ },
inputName: {
type: String,
required: true,
@@ -54,10 +63,14 @@ export default {
return {
pristine: true,
searching: false,
+ hasMoreGroups: true,
+ infiniteScrollLoading: false,
searchString: '',
groups: [],
+ page: 1,
selectedValue: null,
selectedText: null,
+ errorMessage: '',
};
},
computed: {
@@ -74,6 +87,9 @@ export default {
toggleText() {
return this.selectedText ?? this.$options.i18n.toggleText;
},
+ resetButtonLabel() {
+ return this.clearable ? RESET_LABEL : '';
+ },
inputValue() {
return this.selectedValue ? this.selectedValue : '';
},
@@ -95,35 +111,48 @@ export default {
if (this.isSearchQueryTooShort) {
this.groups = [];
} else {
- this.fetchGroups(searchString);
+ this.fetchGroups();
}
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
- async fetchGroups(searchString = '') {
- this.searching = true;
+ async fetchGroups(page = 1) {
+ if (page === 1) {
+ this.searching = true;
+ this.groups = [];
+ this.hasMoreGroups = true;
+ } else {
+ this.infiniteScrollLoading = true;
+ }
try {
- const { data } = await axios.get(
+ const { data, headers } = await axios.get(
Api.buildUrl(groupsPath(this.groupsFilter, this.parentGroupID)),
{
params: {
- search: searchString,
+ search: this.searchString,
+ per_page: GROUPS_PER_PAGE,
+ page,
},
},
);
const groups = data.length ? data : data.results || [];
- this.groups = groups.map((group) => ({
- ...group,
- value: String(group.id),
- }));
+ this.groups.push(
+ ...groups.map((group) => ({
+ ...group,
+ value: String(group.id),
+ })),
+ );
+
+ const { totalPages } = parseIntPagination(normalizeHeaders(headers));
+ if (page === totalPages) {
+ this.hasMoreGroups = false;
+ }
+ this.page = page;
this.searching = false;
+ this.infiniteScrollLoading = false;
} catch (error) {
- createAlert({
- message: FETCH_GROUPS_ERROR,
- error,
- parent: this.$el,
- });
+ this.handleError({ message: FETCH_GROUPS_ERROR, error });
}
},
async fetchInitialSelection() {
@@ -139,11 +168,7 @@ export default {
this.pristine = false;
this.searching = false;
} catch (error) {
- createAlert({
- message: FETCH_GROUP_ERROR,
- error,
- parent: this.$el,
- });
+ this.handleError({ message: FETCH_GROUP_ERROR, error });
}
},
onShown() {
@@ -154,11 +179,20 @@ export default {
onReset() {
this.selected = null;
},
+ onBottomReached() {
+ this.fetchGroups(this.page + 1);
+ },
+ handleError({ message, error }) {
+ Sentry.captureException(error);
+ this.errorMessage = message;
+ },
+ dismissError() {
+ this.errorMessage = '';
+ },
},
i18n: {
toggleText: TOGGLE_TEXT,
selectGroup: __('Select a group'),
- reset: __('Reset'),
noResultsText: __('No results found.'),
searchQueryTooShort: QUERY_TOO_SHORT_MESSAGE,
},
@@ -166,21 +200,27 @@ export default {
</script>
<template>
- <div>
+ <gl-form-group :label="label">
+ <gl-alert v-if="errorMessage" class="gl-mb-3" variant="danger" @dismiss="dismissError">{{
+ errorMessage
+ }}</gl-alert>
<gl-collapsible-listbox
ref="listbox"
v-model="selected"
:header-text="$options.i18n.selectGroup"
- :reset-button-label="$options.i18n.reset"
+ :reset-button-label="resetButtonLabel"
:toggle-text="toggleText"
:loading="searching && pristine"
:searching="searching"
:items="groups"
:no-results-text="noResultsText"
+ :infinite-scroll="hasMoreGroups"
+ :infinite-scroll-loading="infiniteScrollLoading"
searchable
@shown="onShown"
@search="search"
@reset="onReset"
+ @bottom-reached="onBottomReached"
>
<template #list-item="{ item }">
<div class="gl-font-weight-bold">
@@ -189,7 +229,6 @@ export default {
<div class="gl-text-gray-300">{{ item.full_path }}</div>
</template>
</gl-collapsible-listbox>
- <div class="flash-container"></div>
<input :id="inputId" data-testid="input" type="hidden" :name="inputName" :value="inputValue" />
- </div>
+ </gl-form-group>
</template>