summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/feature_flags/components/environments_dropdown.vue
blob: 57727cb945ee826893b879c521ebdab24f97bb75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<script>
import { GlButton, GlSearchBoxByType } from '@gitlab/ui';
import { debounce } from 'lodash';
import { createAlert } from '~/alert';
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();
          createAlert({
            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>