summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/invite_members/components/project_select.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/invite_members/components/project_select.vue')
-rw-r--r--app/assets/javascripts/invite_members/components/project_select.vue143
1 files changed, 143 insertions, 0 deletions
diff --git a/app/assets/javascripts/invite_members/components/project_select.vue b/app/assets/javascripts/invite_members/components/project_select.vue
new file mode 100644
index 00000000000..b7a3918813b
--- /dev/null
+++ b/app/assets/javascripts/invite_members/components/project_select.vue
@@ -0,0 +1,143 @@
+<script>
+import {
+ GlAvatarLabeled,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownText,
+ GlSearchBoxByType,
+} from '@gitlab/ui';
+import { debounce } from 'lodash';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
+import { getProjects } from '~/rest_api';
+import { SEARCH_DELAY, GROUP_FILTERS } from '../constants';
+
+export default {
+ name: 'ProjectSelect',
+ components: {
+ GlAvatarLabeled,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownText,
+ GlSearchBoxByType,
+ },
+ model: {
+ prop: 'selectedProject',
+ },
+ props: {
+ groupsFilter: {
+ type: String,
+ required: false,
+ default: GROUP_FILTERS.ALL,
+ validator: (value) => Object.values(GROUP_FILTERS).includes(value),
+ },
+ parentGroupId: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ isFetching: false,
+ projects: [],
+ selectedProject: {},
+ searchTerm: '',
+ errorMessage: '',
+ };
+ },
+ computed: {
+ selectedProjectName() {
+ return this.selectedProject.name || this.$options.i18n.dropdownText;
+ },
+ isFetchResultEmpty() {
+ return this.projects.length === 0 && !this.isFetching;
+ },
+ },
+ watch: {
+ searchTerm() {
+ this.retrieveProjects();
+ },
+ },
+ mounted() {
+ this.retrieveProjects();
+ },
+ methods: {
+ retrieveProjects: debounce(function debouncedRetrieveProjects() {
+ this.isFetching = true;
+ this.errorMessage = '';
+ return this.fetchProjects()
+ .then((response) => {
+ this.projects = response.data.map((project) => ({
+ ...convertObjectPropsToCamelCase(project),
+ name: project.name_with_namespace,
+ }));
+ })
+ .catch(() => {
+ this.errorMessage = this.$options.i18n.errorFetchingProjects;
+ })
+ .finally(() => {
+ this.isFetching = false;
+ });
+ }, SEARCH_DELAY),
+ fetchProjects() {
+ return getProjects(this.searchTerm, this.$options.defaultFetchOptions);
+ },
+ selectProject(project) {
+ this.selectedProject = project;
+
+ this.$emit('input', this.selectedProject);
+ },
+ },
+ i18n: {
+ dropdownText: s__('ProjectSelect|Select a project'),
+ searchPlaceholder: s__('ProjectSelect|Search projects'),
+ emptySearchResult: s__('ProjectSelect|No matching results'),
+ errorFetchingProjects: s__(
+ 'ProjectSelect|There was an error fetching the projects. Please try again.',
+ ),
+ },
+ defaultFetchOptions: {
+ exclude_internal: true,
+ active: true,
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-dropdown
+ data-testid="project-select-dropdown"
+ :text="selectedProjectName"
+ toggle-class="gl-mb-2"
+ block
+ menu-class="gl-w-full!"
+ >
+ <gl-search-box-by-type
+ v-model="searchTerm"
+ :is-loading="isFetching"
+ :placeholder="$options.i18n.searchPlaceholder"
+ data-qa-selector="project_select_dropdown_search_field"
+ />
+ <gl-dropdown-item
+ v-for="project in projects"
+ :key="project.id"
+ :name="project.name"
+ @click="selectProject(project)"
+ >
+ <gl-avatar-labeled
+ :label="project.name"
+ :src="project.avatarUrl"
+ :entity-id="project.id"
+ :entity-name="project.name"
+ :size="32"
+ />
+ </gl-dropdown-item>
+ <gl-dropdown-text v-if="errorMessage" data-testid="error-message">
+ <span class="gl-text-gray-500">{{ errorMessage }}</span>
+ </gl-dropdown-text>
+ <gl-dropdown-text v-else-if="isFetchResultEmpty" data-testid="empty-result-message">
+ <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span>
+ </gl-dropdown-text>
+ </gl-dropdown>
+ </div>
+</template>