diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-15 18:09:43 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-15 18:09:43 +0000 |
commit | f784f7d3b19fe80834240bde23d1300accb01118 (patch) | |
tree | 2acd4a74cbcde10c5b70c356b146af78362862d1 /app/assets/javascripts | |
parent | 06c127aa72cff78235426341081837cff0b6f78b (diff) | |
download | gitlab-ce-f784f7d3b19fe80834240bde23d1300accb01118.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
14 files changed, 209 insertions, 306 deletions
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 44986c8c575..528475849de 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -1,6 +1,6 @@ <script> import { mapActions, mapState, mapGetters } from 'vuex'; -import { GlModal } from '@gitlab/ui'; +import { GlModal, GlButton } from '@gitlab/ui'; import { deprecatedCreateFlash as flash } from '~/flash'; import { __, sprintf, s__ } from '~/locale'; import { modalTypes } from '../../constants'; @@ -9,6 +9,7 @@ import { trimPathComponents, getPathParent } from '../../utils'; export default { components: { GlModal, + GlButton, }, data() { return { @@ -156,13 +157,14 @@ export default { /> <ul v-if="isCreatingNewFile" class="file-templates gl-mt-3 list-inline qa-template-list"> <li v-for="(template, index) in templateTypes" :key="index" class="list-inline-item"> - <button - type="button" - class="btn btn-missing p-1 pr-2 pl-2" + <gl-button + variant="dashed" + category="secondary" + class="p-1 pr-2 pl-2" @click="createFromTemplate(template)" > {{ template.name }} - </button> + </gl-button> </li> </ul> </div> diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue index f74818ac87c..17f910a9d75 100644 --- a/app/assets/javascripts/integrations/edit/components/integration_form.vue +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -1,6 +1,8 @@ <script> import { mapState, mapActions, mapGetters } from 'vuex'; +import { GlButton } from '@gitlab/ui'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import eventHub from '../event_hub'; import OverrideDropdown from './override_dropdown.vue'; import ActiveCheckbox from './active_checkbox.vue'; @@ -18,11 +20,15 @@ export default { JiraIssuesFields, TriggerFields, DynamicField, + GlButton, }, mixins: [glFeatureFlagsMixin()], computed: { - ...mapGetters(['currentKey', 'propsSource']), - ...mapState(['adminState', 'override']), + ...mapGetters(['currentKey', 'propsSource', 'isSavingOrTesting']), + ...mapState(['adminState', 'override', 'isSaving', 'isTesting']), + isEditable() { + return this.propsSource.editable; + }, isJira() { return this.propsSource.type === 'jira'; }, @@ -31,7 +37,15 @@ export default { }, }, methods: { - ...mapActions(['setOverride']), + ...mapActions(['setOverride', 'setIsSaving', 'setIsTesting']), + onSaveClick() { + this.setIsSaving(true); + eventHub.$emit('saveIntegration'); + }, + onTestClick() { + this.setIsTesting(true); + eventHub.$emit('testIntegration'); + }, }, }; </script> @@ -67,5 +81,29 @@ export default { :key="`${currentKey}-jira-issues-fields`" v-bind="propsSource.jiraIssuesProps" /> + <div v-if="isEditable" class="footer-block row-content-block"> + <gl-button + category="primary" + variant="success" + type="submit" + :loading="isSaving" + :disabled="isSavingOrTesting" + data-qa-selector="save_changes_button" + @click.prevent="onSaveClick" + > + {{ __('Save changes') }} + </gl-button> + <gl-button + v-if="propsSource.canTest" + :loading="isTesting" + :disabled="isSavingOrTesting" + :href="propsSource.testPath" + @click.prevent="onTestClick" + > + {{ __('Test settings') }} + </gl-button> + + <gl-button class="btn-cancel" :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button> + </div> </div> </template> diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index 2ee25bcb1e7..5413226078d 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -24,11 +24,15 @@ function parseDatasetToProps(data) { fields, inheritFromId, integrationLevel, + cancelPath, + testPath, ...booleanAttributes } = data; const { showActive, activated, + editable, + canTest, commitEvents, mergeRequestEvents, enableComments, @@ -41,6 +45,10 @@ function parseDatasetToProps(data) { initialActivated: activated, showActive, type, + cancelPath, + editable, + canTest, + testPath, triggerFieldsProps: { initialTriggerCommit: commitEvents, initialTriggerMergeRequest: mergeRequestEvents, diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js index 1a1873979c9..199c9074ead 100644 --- a/app/assets/javascripts/integrations/edit/store/actions.js +++ b/app/assets/javascripts/integrations/edit/store/actions.js @@ -1,3 +1,5 @@ import * as types from './mutation_types'; export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override); +export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving); +export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting); diff --git a/app/assets/javascripts/integrations/edit/store/getters.js b/app/assets/javascripts/integrations/edit/store/getters.js index b68bd668980..4b494c30845 100644 --- a/app/assets/javascripts/integrations/edit/store/getters.js +++ b/app/assets/javascripts/integrations/edit/store/getters.js @@ -1,5 +1,7 @@ export const isInheriting = state => (state.adminState === null ? false : !state.override); +export const isSavingOrTesting = state => state.isSaving || state.isTesting; + export const propsSource = (state, getters) => getters.isInheriting ? state.adminState : state.customState; diff --git a/app/assets/javascripts/integrations/edit/store/mutation_types.js b/app/assets/javascripts/integrations/edit/store/mutation_types.js index dfba3c6d640..0dae8ea079e 100644 --- a/app/assets/javascripts/integrations/edit/store/mutation_types.js +++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js @@ -1 +1,3 @@ export const SET_OVERRIDE = 'SET_OVERRIDE'; +export const SET_IS_SAVING = 'SET_IS_SAVING'; +export const SET_IS_TESTING = 'SET_IS_TESTING'; diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js index 8757d415197..8ac3c476f9e 100644 --- a/app/assets/javascripts/integrations/edit/store/mutations.js +++ b/app/assets/javascripts/integrations/edit/store/mutations.js @@ -4,4 +4,10 @@ export default { [types.SET_OVERRIDE](state, override) { state.override = override; }, + [types.SET_IS_SAVING](state, isSaving) { + state.isSaving = isSaving; + }, + [types.SET_IS_TESTING](state, isTesting) { + state.isTesting = isTesting; + }, }; diff --git a/app/assets/javascripts/integrations/edit/store/state.js b/app/assets/javascripts/integrations/edit/store/state.js index 95c1a2be500..7f1cd5c6e4d 100644 --- a/app/assets/javascripts/integrations/edit/store/state.js +++ b/app/assets/javascripts/integrations/edit/store/state.js @@ -5,5 +5,7 @@ export default ({ adminState = null, customState = {} } = {}) => { override, adminState, customState, + isSaving: false, + isTesting: false, }; }; diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index 1135065b06c..529358a5f0b 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import axios from '../lib/utils/axios_utils'; -import { deprecatedCreateFlash as flash } from '../flash'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; +import toast from '~/vue_shared/plugins/global_toast'; import initForm from './edit'; import eventHub from './edit/event_hub'; @@ -10,65 +10,63 @@ export default class IntegrationSettingsForm { this.$form = $(formSelector); this.formActive = false; + this.vue = null; + // Form Metadata - this.canTestService = this.$form.data('canTest'); this.testEndPoint = this.$form.data('testUrl'); - - // Form Child Elements - this.$submitBtn = this.$form.find('button[type="submit"]'); - this.$submitBtnLoader = this.$submitBtn.find('.js-btn-spinner'); - this.$submitBtnLabel = this.$submitBtn.find('.js-btn-label'); } init() { // Init Vue component - initForm( + this.vue = initForm( document.querySelector('.js-vue-integration-settings'), document.querySelector('.js-vue-admin-integration-settings'), ); eventHub.$on('toggle', active => { this.formActive = active; - this.handleServiceToggle(); + this.toggleServiceState(); + }); + eventHub.$on('testIntegration', () => { + this.testIntegration(); + }); + eventHub.$on('saveIntegration', () => { + this.saveIntegration(); }); - - // Bind Event Listeners - this.$submitBtn.on('click', e => this.handleSettingsSave(e)); } - handleSettingsSave(e) { - // Check if Service is marked active, as if not marked active, - // We can skip testing it and directly go ahead to allow form to - // be submitted - if (!this.formActive) { - return; + saveIntegration() { + // Service was marked active so now we check; + // 1) If form contents are valid + // 2) If this service can be saved + // If both conditions are true, we override form submission + // and save the service using provided configuration. + if (this.$form.get(0).checkValidity()) { + this.$form.submit(); + } else { + eventHub.$emit('validateForm'); + this.vue.$store.dispatch('setIsSaving', false); } + } + testIntegration() { // Service was marked active so now we check; // 1) If form contents are valid // 2) If this service can be tested // If both conditions are true, we override form submission // and test the service using provided configuration. if (this.$form.get(0).checkValidity()) { - if (this.canTestService) { - e.preventDefault(); - // eslint-disable-next-line no-jquery/no-serialize - this.testSettings(this.$form.serialize()); - } + // eslint-disable-next-line no-jquery/no-serialize + this.testSettings(this.$form.serialize()); } else { - e.preventDefault(); eventHub.$emit('validateForm'); + this.vue.$store.dispatch('setIsTesting', false); } } - handleServiceToggle() { - this.toggleServiceState(); - } - /** * Change Form's validation enforcement based on service status (active/inactive) */ toggleServiceState() { - this.toggleSubmitBtnLabel(); if (this.formActive) { this.$form.removeAttr('novalidate'); } else if (!this.$form.attr('novalidate')) { @@ -77,67 +75,23 @@ export default class IntegrationSettingsForm { } /** - * Toggle Submit button label based on Integration status and ability to test service - */ - toggleSubmitBtnLabel() { - let btnLabel = __('Save changes'); - - if (this.formActive && this.canTestService) { - btnLabel = __('Test settings and save changes'); - } - - this.$submitBtnLabel.text(btnLabel); - } - - /** - * Toggle Submit button state based on provided boolean value of `saveTestActive` - * When enabled, it does two things, and reverts back when disabled - * - * 1. It shows load spinner on submit button - * 2. Makes submit button disabled - */ - toggleSubmitBtnState(saveTestActive) { - if (saveTestActive) { - this.$submitBtn.disable(); - this.$submitBtnLoader.removeClass('hidden'); - } else { - this.$submitBtn.enable(); - this.$submitBtnLoader.addClass('hidden'); - } - } - - /** * Test Integration config */ testSettings(formData) { - this.toggleSubmitBtnState(true); - return axios .put(this.testEndPoint, formData) .then(({ data }) => { if (data.error) { - let flashActions; - - if (data.test_failed) { - flashActions = { - title: __('Save anyway'), - clickHandler: e => { - e.preventDefault(); - this.$form.submit(); - }, - }; - } - - flash(`${data.message} ${data.service_response}`, 'alert', document, flashActions); + toast(`${data.message} ${data.service_response}`); } else { - this.$form.submit(); + toast(s__('Integrations|Connection successful.')); } - - this.toggleSubmitBtnState(false); }) .catch(() => { - flash(__('Something went wrong on our end.')); - this.toggleSubmitBtnState(false); + toast(__('Something went wrong on our end.')); + }) + .finally(() => { + this.vue.$store.dispatch('setIsTesting', false); }); } } diff --git a/app/assets/javascripts/lib/utils/axios_startup_calls.js b/app/assets/javascripts/lib/utils/axios_startup_calls.js index 642bd948b61..7e2665b910c 100644 --- a/app/assets/javascripts/lib/utils/axios_startup_calls.js +++ b/app/assets/javascripts/lib/utils/axios_startup_calls.js @@ -10,6 +10,32 @@ const getFullUrl = req => { return mergeUrlParams(req.params || {}, url); }; +const handleStartupCall = async ({ fetchCall }, req) => { + const res = await fetchCall; + if (!res.ok) { + throw new Error(res.statusText); + } + + const fetchHeaders = {}; + res.headers.forEach((val, key) => { + fetchHeaders[key] = val; + }); + + const data = await res.clone().json(); + + Object.assign(req, { + adapter: () => + Promise.resolve({ + data, + status: res.status, + statusText: res.statusText, + headers: fetchHeaders, + config: req, + request: req, + }), + }); +}; + const setupAxiosStartupCalls = axios => { const { startup_calls: startupCalls } = window.gl || {}; @@ -17,38 +43,28 @@ const setupAxiosStartupCalls = axios => { return; } - // TODO: To save performance of future axios calls, we can - // remove this interceptor once the "startupCalls" have been loaded - axios.interceptors.request.use(req => { + const remainingCalls = new Map(Object.entries(startupCalls)); + + const interceptor = axios.interceptors.request.use(async req => { const fullUrl = getFullUrl(req); - const existing = startupCalls[fullUrl]; - - if (existing) { - // eslint-disable-next-line no-param-reassign - req.adapter = () => - existing.fetchCall.then(res => { - const fetchHeaders = {}; - res.headers.forEach((val, key) => { - fetchHeaders[key] = val; - }); - - // We can delete it as it anyhow should only be called once - delete startupCalls[fullUrl]; - - // eslint-disable-next-line promise/no-nesting - return res - .clone() - .json() - .then(data => ({ - data, - status: res.status, - statusText: res.statusText, - headers: fetchHeaders, - config: req, - request: req, - })); - }); + const startupCall = remainingCalls.get(fullUrl); + + if (!startupCall?.fetchCall) { + return req; + } + + try { + await handleStartupCall(startupCall, req); + } catch (e) { + // eslint-disable-next-line no-console + console.warn(`[gitlab] Something went wrong with the startup call for "${fullUrl}"`, e); + } + + remainingCalls.delete(fullUrl); + + if (remainingCalls.size === 0) { + axios.interceptors.request.eject(interceptor); } return req; diff --git a/app/assets/javascripts/packages/details/store/getters.js b/app/assets/javascripts/packages/details/store/getters.js index d1814d506ad..ede6d39bde7 100644 --- a/app/assets/javascripts/packages/details/store/getters.js +++ b/app/assets/javascripts/packages/details/store/getters.js @@ -84,10 +84,10 @@ export const npmSetupCommand = ({ packageEntity, npmPath }) => (type = NpmManage const scope = packageEntity.name.substring(0, packageEntity.name.indexOf('/')); if (type === NpmManager.NPM) { - return `echo ${scope}:registry=${npmPath} >> .npmrc`; + return `echo ${scope}:registry=${npmPath}/ >> .npmrc`; } - return `echo \\"${scope}:registry\\" \\"${npmPath}\\" >> .yarnrc`; + return `echo \\"${scope}:registry\\" \\"${npmPath}/\\" >> .yarnrc`; }; export const nugetInstallationCommand = ({ packageEntity }) => diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue index b12250d1d1c..157d6d60290 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue @@ -1,17 +1,23 @@ <script> -import { GlLink } from '@gitlab/ui'; -import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue'; +import { GlButtonGroup, GlDropdown, GlDropdownItem, GlLink, GlSearchBoxByType } from '@gitlab/ui'; +import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import ReviewAppLink from '../review_app_link.vue'; export default { name: 'DeploymentViewButton', components: { - FilteredSearchDropdown, + GlButtonGroup, + GlDropdown, + GlDropdownItem, GlLink, + GlSearchBoxByType, ReviewAppLink, VisualReviewAppLink: () => import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'), }, + directives: { + autofocusonshow, + }, props: { appButtonText: { type: Object, @@ -37,6 +43,9 @@ export default { }), }, }, + data() { + return { searchTerm: '' }; + }, computed: { deploymentExternalUrl() { if (this.deployment.changes && this.deployment.changes.length === 1) { @@ -47,44 +56,52 @@ export default { shouldRenderDropdown() { return this.deployment.changes && this.deployment.changes.length > 1; }, + filteredChanges() { + return this.deployment?.changes?.filter(change => change.path.includes(this.searchTerm)); + }, }, }; </script> - <template> <span> - <filtered-search-dropdown - v-if="shouldRenderDropdown" - class="js-mr-wigdet-deployment-dropdown inline" - :items="deployment.changes" - :main-action-link="deploymentExternalUrl" - filter-key="path" - > - <template #mainAction="{ className }"> - <review-app-link - :display="appButtonText" - :link="deploymentExternalUrl" - :css-class="`deploy-link js-deploy-url inline ${className}`" + <gl-button-group v-if="shouldRenderDropdown" size="small"> + <review-app-link + :display="appButtonText" + :link="deploymentExternalUrl" + size="small" + css-class="deploy-link js-deploy-url inline" + /> + <gl-dropdown size="small" class="js-mr-wigdet-deployment-dropdown"> + <gl-search-box-by-type + v-model.trim="searchTerm" + v-autofocusonshow + autofocus + class="gl-m-3" /> - </template> - - <template #result="{ result }"> - <gl-link - :href="result.external_url" - target="_blank" - rel="noopener noreferrer nofollow" - class="js-deploy-url-menu-item menu-item" + <gl-dropdown-item + v-for="change in filteredChanges" + :key="change.path" + class="js-filtered-dropdown-result" > - <strong class="str-truncated-100 gl-mb-0 d-block">{{ result.path }}</strong> - - <p class="text-secondary str-truncated-100 gl-mb-0 d-block">{{ result.external_url }}</p> - </gl-link> - </template> - </filtered-search-dropdown> + <gl-link + :href="change.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="js-deploy-url-menu-item menu-item" + > + <strong class="str-truncated-100 gl-mb-0 gl-display-block">{{ change.path }}</strong> + <p class="text-secondary str-truncated-100 gl-mb-0 d-block"> + {{ change.external_url }} + </p> + </gl-link> + </gl-dropdown-item> + </gl-dropdown> + </gl-button-group> <review-app-link v-else :display="appButtonText" :link="deploymentExternalUrl" + size="small" css-class="js-deploy-url deploy-link btn btn-default btn-sm inline" /> <visual-review-app-link diff --git a/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue b/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue index 16ac1e7fbb1..ebd2b5cd22d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue @@ -1,8 +1,9 @@ <script> -import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; export default { components: { + GlButton, GlIcon, }, directives: { @@ -21,14 +22,20 @@ export default { type: String, required: true, }, + size: { + type: String, + required: false, + default: 'medium', + }, }, }; </script> <template> - <a + <gl-button v-gl-tooltip :title="display.tooltip" :href="link" + :size="size" target="_blank" rel="noopener noreferrer nofollow" :class="cssClass" @@ -36,5 +43,5 @@ export default { data-track-label="review_app" > {{ display.text }} <gl-icon class="fgray" name="external-link" /> - </a> + </gl-button> </template> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue deleted file mode 100644 index ee2687ace0e..00000000000 --- a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue +++ /dev/null @@ -1,153 +0,0 @@ -<script> -import $ from 'jquery'; -import { GlDeprecatedButton, GlIcon } from '@gitlab/ui'; -import { __ } from '~/locale'; -/** - * Renders a split dropdown with - * an input that allows to search through the given - * array of options. - * - * When there are no results and `showCreateMode` is true - * it renders a create button with the value typed. - */ -export default { - name: 'FilteredSearchDropdown', - components: { - GlIcon, - GlDeprecatedButton, - }, - props: { - title: { - type: String, - required: false, - default: '', - }, - buttonType: { - required: false, - validator: value => - ['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf( - value, - ) !== -1, - default: 'default', - }, - size: { - required: false, - type: String, - default: 'sm', - }, - items: { - type: Array, - required: true, - }, - visibleItems: { - type: Number, - required: false, - default: 5, - }, - filterKey: { - type: String, - required: true, - }, - showCreateMode: { - type: Boolean, - required: false, - default: false, - }, - createButtonText: { - type: String, - required: false, - default: __('Create'), - }, - }, - data() { - return { - filter: '', - }; - }, - computed: { - className() { - return `btn btn-${this.buttonType} btn-${this.size}`; - }, - filteredResults() { - if (this.filter !== '') { - return this.items.filter( - item => - item[this.filterKey] && - item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()), - ); - } - - return this.items.slice(0, this.visibleItems); - }, - computedCreateButtonText() { - return `${this.createButtonText} ${this.filter}`; - }, - shouldRenderCreateButton() { - return this.showCreateMode && this.filteredResults.length === 0 && this.filter !== ''; - }, - }, - mounted() { - /** - * Resets the filter every time the user closes the dropdown - */ - $(this.$el) - .on('shown.bs.dropdown', () => { - this.$nextTick(() => this.$refs.searchInput.focus()); - }) - .on('hidden.bs.dropdown', () => { - this.filter = ''; - }); - }, -}; -</script> -<template> - <div class="dropdown"> - <div class="btn-group"> - <slot name="mainAction" :class-name="className"> - <button type="button" :class="className">{{ title }}</button> - </slot> - - <button - type="button" - :class="className" - class="dropdown-toggle dropdown-toggle-split" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false" - :aria-label="__('Expand dropdown')" - > - <gl-icon name="angle-down" :size="12" /> - </button> - <div class="dropdown-menu dropdown-menu-right"> - <div class="dropdown-input"> - <input - ref="searchInput" - v-model="filter" - type="search" - :placeholder="__('Filter')" - class="js-filtered-dropdown-input dropdown-input-field" - /> - <gl-icon class="dropdown-input-search" name="search" /> - </div> - - <div class="dropdown-content"> - <ul> - <li v-for="(result, i) in filteredResults" :key="i" class="js-filtered-dropdown-result"> - <slot name="result" :result="result">{{ result[filterKey] }}</slot> - </li> - </ul> - </div> - - <div v-if="shouldRenderCreateButton" class="dropdown-footer"> - <slot name="footer" :filter="filter"> - <gl-deprecated-button - class="js-dropdown-create-button btn-transparent" - @click="$emit('createItem', filter)" - >{{ computedCreateButtonText }}</gl-deprecated-button - > - </slot> - </div> - </div> - </div> - </div> -</template> |