gitlab-org--gitlab-foss/app/assets/javascripts/feature_flags/components/environments_dropdown.vue

185 lines
4.8 KiB
Vue

<script>
import { GlButton, GlSearchBoxByType } from '@gitlab/ui';
import { debounce } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
/**
* Creates a searchable input for environments.
*
* When given a value, it will render it as selected value
* Otherwise it will render a placeholder for the search input.
* It will fetch the available environments on focus.
*
* When the user types, it will trigger an event to allow
* for API queries outside of the component.
*
* When results are returned, it renders a selectable
* list with the suggestions
*
* When no results are returned, it will render a
* button with a `Create` label. When clicked, it will
* emit an event to allow for the creation of a new
* record.
*
*/
export default {
name: 'EnvironmentsSearchableInput',
components: {
GlButton,
GlSearchBoxByType,
},
inject: ['environmentsEndpoint'],
props: {
value: {
type: String,
required: false,
default: '',
},
placeholder: {
type: String,
required: false,
default: __('Search an environment spec'),
},
createButtonLabel: {
type: String,
required: false,
default: __('Create'),
},
disabled: {
type: Boolean,
default: false,
required: false,
},
},
data() {
return {
environmentSearch: this.value,
results: [],
showSuggestions: false,
isLoading: false,
};
},
computed: {
/**
* Creates a label with the value of the filter
* @returns {String}
*/
composedCreateButtonLabel() {
return `${this.createButtonLabel} ${this.environmentSearch}`;
},
shouldRenderCreateButton() {
return !this.isLoading && !this.results.length;
},
},
methods: {
fetchEnvironments: debounce(function debouncedFetchEnvironments() {
this.isLoading = true;
this.openSuggestions();
axios
.get(this.environmentsEndpoint, { params: { query: this.environmentSearch } })
.then(({ data }) => {
this.results = data || [];
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
this.closeSuggestions();
createFlash({
message: __('Something went wrong on our end. Please try again.'),
});
});
}, 250),
/**
* Opens the list of suggestions
*/
openSuggestions() {
this.showSuggestions = true;
},
/**
* Closes the list of suggestions and cleans the results
*/
closeSuggestions() {
this.showSuggestions = false;
this.environmentSearch = '';
},
/**
* On click, it will:
* 1. clear the input value
* 2. close the list of suggestions
* 3. emit an event
*/
clearInput() {
this.closeSuggestions();
this.$emit('clearInput');
},
/**
* When the user selects a value from the list of suggestions
*
* It emits an event with the selected value
* Clears the filter
* and closes the list of suggestions
*
* @param {String} selected
*/
selectEnvironment(selected) {
this.$emit('selectEnvironment', selected);
this.results = [];
this.closeSuggestions();
},
/**
* When the user clicks the create button
* it emits an event with the filter value
*/
createClicked() {
this.$emit('createClicked', this.environmentSearch);
this.closeSuggestions();
},
},
};
</script>
<template>
<div>
<div class="dropdown position-relative">
<gl-search-box-by-type
v-model.trim="environmentSearch"
class="js-env-search"
:aria-label="placeholder"
:placeholder="placeholder"
:disabled="disabled"
:is-loading="isLoading"
@focus="fetchEnvironments"
@keyup="fetchEnvironments"
/>
<div
v-if="showSuggestions"
class="dropdown-menu d-block dropdown-menu-selectable dropdown-menu-full-width"
>
<div class="dropdown-content">
<ul v-if="results.length">
<li v-for="(result, i) in results" :key="i">
<gl-button category="tertiary" @click="selectEnvironment(result)">{{
result
}}</gl-button>
</li>
</ul>
<div v-else-if="!results.length" class="text-secondary gl-p-3">
{{ __('No matching results') }}
</div>
<div v-if="shouldRenderCreateButton" class="dropdown-footer">
<gl-button
category="tertiary"
class="js-create-button dropdown-item"
@click="createClicked"
>{{ composedCreateButtonLabel }}</gl-button
>
</div>
</div>
</div>
</div>
</div>
</template>