diff options
Diffstat (limited to 'app/assets/javascripts/feature_flags/components/environments_dropdown.vue')
-rw-r--r-- | app/assets/javascripts/feature_flags/components/environments_dropdown.vue | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/app/assets/javascripts/feature_flags/components/environments_dropdown.vue b/app/assets/javascripts/feature_flags/components/environments_dropdown.vue new file mode 100644 index 00000000000..3caf536b6a2 --- /dev/null +++ b/app/assets/javascripts/feature_flags/components/environments_dropdown.vue @@ -0,0 +1,181 @@ +<script> +import { debounce } from 'lodash'; +import { GlDeprecatedButton, GlSearchBoxByType } from '@gitlab/ui'; +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; + +/** + * 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: { + GlDeprecatedButton, + GlSearchBoxByType, + }, + 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, + }, + }, + inject: ['environmentsEndpoint'], + 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(__('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-deprecated-button class="btn-transparent" @click="selectEnvironment(result)">{{ + result + }}</gl-deprecated-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-deprecated-button + class="js-create-button btn-blank dropdown-item" + @click="createClicked" + >{{ composedCreateButtonLabel }}</gl-deprecated-button + > + </div> + </div> + </div> + </div> + </div> +</template> |