summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/feature_flags/components/environments_dropdown.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/feature_flags/components/environments_dropdown.vue')
-rw-r--r--app/assets/javascripts/feature_flags/components/environments_dropdown.vue181
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>