diff options
Diffstat (limited to 'app/assets/javascripts')
16 files changed, 319 insertions, 85 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 33f6afc9c2d..ff1e1805948 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,5 +1,12 @@ <script> -import { GlDropdown, GlDropdownItem, GlLink } from '@gitlab/ui'; +import { + GlButton, + GlDropdown, + GlDropdownItem, + GlModal, + GlModalDirective, + GlLink, +} from '@gitlab/ui'; import _ from 'underscore'; import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; @@ -23,12 +30,21 @@ export default { GraphGroup, EmptyState, Icon, + GlButton, GlDropdown, GlDropdownItem, GlLink, + GlModal, + }, + directives: { + GlModalDirective, }, - props: { + externalDashboardPath: { + type: String, + required: false, + default: '', + }, hasMetrics: { type: Boolean, required: false, @@ -96,6 +112,19 @@ export default { type: Boolean, required: true, }, + customMetricsAvailable: { + type: Boolean, + required: false, + default: false, + }, + customMetricsPath: { + type: String, + required: true, + }, + validateQueryPath: { + type: String, + required: true, + }, }, data() { return { @@ -105,8 +134,14 @@ export default { elWidth: 0, selectedTimeWindow: '', selectedTimeWindowKey: '', + formIsValid: null, }; }, + computed: { + canAddMetrics() { + return this.customMetricsAvailable && this.customMetricsPath.length; + }, + }, created() { this.service = new MonitoringService({ metricsEndpoint: this.metricsEndpoint, @@ -187,11 +222,20 @@ export default { this.state = 'unableToConnect'; }); }, + hideAddMetricModal() { + this.$refs.addMetricModal.hide(); + }, onSidebarMutation() { setTimeout(() => { this.elWidth = this.$el.clientWidth; }, sidebarAnimationDuration); }, + setFormValidity(isValid) { + this.formIsValid = isValid; + }, + submitCustomMetricsForm() { + this.$refs.customMetricsForm.submit(); + }, activeTimeWindow(key) { return this.timeWindows[key] === this.selectedTimeWindow; }, @@ -199,47 +243,96 @@ export default { return `?time_window=${key}`; }, }, + addMetric: { + title: s__('Metrics|Add metric'), + modalId: 'add-metric', + }, }; </script> <template> - <div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default"> - <div - v-if="environmentsEndpoint" - class="dropdowns d-flex align-items-center justify-content-between" - > - <div class="d-flex align-items-center"> - <strong>{{ s__('Metrics|Environment') }}</strong> - <gl-dropdown - class="prepend-left-10 js-environments-dropdown" - toggle-class="dropdown-menu-toggle" - :text="currentEnvironmentName" - :disabled="store.environmentsData.length === 0" - > - <gl-dropdown-item - v-for="environment in store.environmentsData" - :key="environment.id" - :href="environment.metrics_path" - :active="environment.name === currentEnvironmentName" - active-class="is-active" - >{{ environment.name }}</gl-dropdown-item + <div v-if="!showEmptyState" class="prometheus-graphs"> + <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between"> + <div + v-if="environmentsEndpoint" + class="dropdowns d-flex align-items-center justify-content-between" + > + <div class="d-flex align-items-center"> + <strong>{{ s__('Metrics|Environment') }}</strong> + <gl-dropdown + class="prepend-left-10 js-environments-dropdown" + toggle-class="dropdown-menu-toggle" + :text="currentEnvironmentName" + :disabled="store.environmentsData.length === 0" + > + <gl-dropdown-item + v-for="environment in store.environmentsData" + :key="environment.id" + :active="environment.name === currentEnvironmentName" + active-class="is-active" + >{{ environment.name }}</gl-dropdown-item + > + </gl-dropdown> + </div> + <div v-if="showTimeWindowDropdown" class="d-flex align-items-center"> + <strong>{{ s__('Metrics|Show last') }}</strong> + <gl-dropdown + class="prepend-left-10 js-time-window-dropdown" + toggle-class="dropdown-menu-toggle" + :text="selectedTimeWindow" > - </gl-dropdown> + <gl-dropdown-item + v-for="(value, key) in timeWindows" + :key="key" + :active="activeTimeWindow(key)" + ><gl-link :href="setTimeWindowParameter(key)">{{ value }}</gl-link></gl-dropdown-item + > + </gl-dropdown> + </div> </div> - <div v-if="showTimeWindowDropdown" class="d-flex align-items-center"> - <strong>{{ s__('Metrics|Show last') }}</strong> - <gl-dropdown - class="prepend-left-10 js-time-window-dropdown" - toggle-class="dropdown-menu-toggle" - :text="selectedTimeWindow" - > - <gl-dropdown-item - v-for="(value, key) in timeWindows" - :key="key" - :active="activeTimeWindow(key)" - ><gl-link :href="setTimeWindowParameter(key)">{{ value }}</gl-link></gl-dropdown-item + <div class="d-flex"> + <div v-if="isEE && canAddMetrics"> + <gl-button + v-gl-modal-directive="$options.addMetric.modalId" + class="js-add-metric-button text-success border-success" + > + {{ $options.addMetric.title }} + </gl-button> + <gl-modal + ref="addMetricModal" + :modal-id="$options.addMetric.modalId" + :title="$options.addMetric.title" > - </gl-dropdown> + <form ref="customMetricsForm" :action="customMetricsPath" method="post"> + <custom-metrics-form-fields + :validate-query-path="validateQueryPath" + form-operation="post" + @formValidation="setFormValidity" + /> + </form> + <div slot="modal-footer"> + <gl-button @click="hideAddMetricModal"> + {{ __('Cancel') }} + </gl-button> + <gl-button + :disabled="!formIsValid" + variant="success" + @click="submitCustomMetricsForm" + > + {{ __('Save changes') }} + </gl-button> + </div> + </gl-modal> + </div> + <gl-button + v-if="externalDashboardPath.length" + class="js-external-dashboard-link prepend-left-8" + variant="primary" + :href="externalDashboardPath" + > + {{ __('View full dashboard') }} + <icon name="external-link" /> + </gl-button> </div> </div> <graph-group diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index 8ddd5b8514a..88454c3fb4c 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -83,10 +83,12 @@ export default { formCancelHandler(shouldConfirm, isDirty) { this.$emit('cancelForm', shouldConfirm, isDirty); }, - applySuggestion({ suggestionId, flashContainer, callback }) { + applySuggestion({ suggestionId, flashContainer, callback = () => {} }) { const { discussion_id: discussionId, id: noteId } = this.note; - this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer, callback }); + return this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer }).then( + callback, + ); }, }, }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 970e6551092..63658d49a05 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -142,6 +142,23 @@ export const createNewNote = ({ commit, dispatch }, { endpoint, data }) => export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES); +export const resolveDiscussion = ({ state, dispatch, getters }, { discussionId }) => { + const discussion = utils.findNoteObjectById(state.discussions, discussionId); + const isResolved = getters.isDiscussionResolved(discussionId); + + if (!discussion) { + return Promise.reject(); + } else if (isResolved) { + return Promise.resolve(); + } + + return dispatch('toggleResolveNote', { + endpoint: discussion.resolve_path, + isResolved, + discussion: true, + }); +}; + export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) => service .toggleResolveNote(endpoint, isResolved) @@ -251,11 +268,20 @@ export const saveNote = ({ commit, dispatch }, noteData) => { const { errors } = res; const commandsChanges = res.commands_changes; - if (hasQuickActions && errors && Object.keys(errors).length) { - eTagPoll.makeRequest(); + if (errors && Object.keys(errors).length) { + /* + The following reply means that quick actions have been successfully applied: + + {"commands_changes":{},"valid":false,"errors":{"commands_only":["Commands applied"]}} + */ + if (hasQuickActions) { + eTagPoll.makeRequest(); - $('.js-gfm-input').trigger('clear-commands-cache.atwho'); - Flash(__('Commands applied'), 'notice', noteData.flashContainer); + $('.js-gfm-input').trigger('clear-commands-cache.atwho'); + Flash(__('Commands applied'), 'notice', noteData.flashContainer); + } else { + throw new Error(__('Failed to save comment!')); + } } if (commandsChanges) { @@ -420,15 +446,13 @@ export const updateResolvableDiscussonsCounts = ({ commit }) => commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS); export const submitSuggestion = ( - { commit }, - { discussionId, noteId, suggestionId, flashContainer, callback }, -) => { + { commit, dispatch }, + { discussionId, noteId, suggestionId, flashContainer }, +) => service .applySuggestion(suggestionId) - .then(() => { - commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }); - callback(); - }) + .then(() => commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId })) + .then(() => dispatch('resolveDiscussion', { discussionId }).catch(() => {})) .catch(err => { const defaultMessage = __( 'Something went wrong while applying the suggestion. Please try again.', @@ -436,9 +460,7 @@ export const submitSuggestion = ( const flashMessage = err.response.data ? `${err.response.data.message}.` : defaultMessage; Flash(__(flashMessage), 'alert', flashContainer); - callback(); }); -}; export const convertToDiscussion = ({ commit }, noteId) => commit(types.CONVERT_TO_DISCUSSION, noteId); diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index 029fde348fb..ed4cef4a917 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -2,7 +2,8 @@ import AjaxCache from '~/lib/utils/ajax_cache'; import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; import { sprintf, __ } from '~/locale'; -const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; +// factory function because global flag makes RegExp stateful +const createQuickActionsRegex = () => /^\/\w+.*$/gm; export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0]; @@ -27,9 +28,9 @@ export const getQuickActionText = note => { return text; }; -export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note); +export const hasQuickActions = note => createQuickActionsRegex().test(note); -export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim(); +export const stripQuickActions = note => note.replace(createQuickActionsRegex(), '').trim(); export const prepareDiffLines = diffLines => diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) })); diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue new file mode 100644 index 00000000000..0a87d193b72 --- /dev/null +++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue @@ -0,0 +1,57 @@ +<script> +import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui'; + +export default { + components: { + GlButton, + GlFormGroup, + GlFormInput, + GlLink, + }, + props: { + externalDashboardPath: { + type: String, + required: false, + default: '', + }, + externalDashboardHelpPagePath: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <section class="settings expanded"> + <div class="settings-header"> + <h4 class="js-section-header"> + {{ s__('ExternalMetrics|External Dashboard') }} + </h4> + <p class="js-section-sub-header"> + {{ + s__( + 'ExternalMetrics|Add a button to the metrics dashboard linking directly to your existing external dashboards.', + ) + }} + <gl-link :href="externalDashboardHelpPagePath">{{ __('Learn more') }}</gl-link> + </p> + </div> + <div class="settings-content"> + <form> + <gl-form-group + :label="s__('ExternalMetrics|Full dashboard URL')" + :description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')" + > + <gl-form-input + :value="externalDashboardPath" + placeholder="https://my-org.gitlab.io/my-dashboards" + /> + </gl-form-group> + <gl-button variant="success"> + {{ __('Save Changes') }} + </gl-button> + </form> + </div> + </section> +</template> diff --git a/app/assets/javascripts/operation_settings/index.js b/app/assets/javascripts/operation_settings/index.js new file mode 100644 index 00000000000..1171f3ece9f --- /dev/null +++ b/app/assets/javascripts/operation_settings/index.js @@ -0,0 +1,26 @@ +import Vue from 'vue'; +import ExternalDashboardForm from './components/external_dashboard.vue'; + +export default () => { + /** + * This check can be removed when we remove + * the :grafana_dashboard_link feature flag + */ + if (!gon.features.grafanaDashboardLink) { + return null; + } + + const el = document.querySelector('.js-operation-settings'); + + return new Vue({ + el, + render(createElement) { + return createElement(ExternalDashboardForm, { + props: { + ...el.dataset, + expanded: false, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/pages/profiles/keys/index.js b/app/assets/javascripts/pages/profiles/keys/index.js index 1cd3ee1dfdb..d3dcd21f456 100644 --- a/app/assets/javascripts/pages/profiles/keys/index.js +++ b/app/assets/javascripts/pages/profiles/keys/index.js @@ -2,6 +2,8 @@ import AddSshKeyValidation from '~/profile/add_ssh_key_validation'; document.addEventListener('DOMContentLoaded', () => { const input = document.querySelector('.js-add-ssh-key-validation-input'); + if (!input) return; + const warning = document.querySelector('.js-add-ssh-key-validation-warning'); const originalSubmit = input.form.querySelector('.js-add-ssh-key-validation-original-submit'); const confirmSubmit = warning.querySelector('.js-add-ssh-key-validation-confirm-submit'); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js index c1f6edf2f27..a20a0526f12 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js @@ -1,4 +1,10 @@ -const defaultTimezone = 'UTC'; +const defaultTimezone = { name: 'UTC', offset: 0 }; +const defaults = { + $inputEl: null, + $dropdownEl: null, + onSelectTimezone: null, + displayFormat: item => item.name, +}; export const formatUtcOffset = offset => { const parsed = parseInt(offset, 10); @@ -11,23 +17,28 @@ export const formatUtcOffset = offset => { export const formatTimezone = item => `[UTC ${formatUtcOffset(item.offset)}] ${item.name}`; -const defaults = { - $inputEl: null, - $dropdownEl: null, - onSelectTimezone: null, +export const findTimezoneByIdentifier = (tzList = [], identifier = null) => { + if (tzList && tzList.length && identifier && identifier.length) { + return tzList.find(tz => tz.identifier === identifier) || null; + } + return null; }; export default class TimezoneDropdown { - constructor({ $dropdownEl, $inputEl, onSelectTimezone } = defaults) { + constructor({ $dropdownEl, $inputEl, onSelectTimezone, displayFormat } = defaults) { this.$dropdown = $dropdownEl; this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); this.$input = $inputEl; this.timezoneData = this.$dropdown.data('data'); + this.onSelectTimezone = onSelectTimezone; + this.displayFormat = displayFormat || defaults.displayFormat; + + this.initialTimezone = + findTimezoneByIdentifier(this.timezoneData, this.$input.val()) || defaultTimezone; + this.initDefaultTimezone(); this.initDropdown(); - - this.onSelectTimezone = onSelectTimezone; } initDropdown() { @@ -35,7 +46,7 @@ export default class TimezoneDropdown { data: this.timezoneData, filterable: true, selectable: true, - toggleLabel: item => item.name, + toggleLabel: this.displayFormat, search: { fields: ['name'], }, @@ -43,20 +54,17 @@ export default class TimezoneDropdown { text: item => formatTimezone(item), }); - this.setDropdownToggle(); + this.setDropdownToggle(this.displayFormat(this.initialTimezone)); } initDefaultTimezone() { - const initialValue = this.$input.val(); - - if (!initialValue) { - this.$input.val(defaultTimezone); + if (!this.$input.val()) { + this.$input.val(defaultTimezone.name); } } - setDropdownToggle() { - const initialValue = this.$input.val(); - this.$dropdownToggle.text(initialValue); + setDropdownToggle(dropdownText) { + this.$dropdownToggle.text(dropdownText); } updateInputValue({ selectedObj, e }) { diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js index 73c745179be..5270a7924ec 100644 --- a/app/assets/javascripts/pages/projects/settings/operations/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js @@ -1,5 +1,7 @@ import mountErrorTrackingForm from '~/error_tracking_settings'; +import mountOperationSettings from '~/operation_settings'; document.addEventListener('DOMContentLoaded', () => { mountErrorTrackingForm(); + mountOperationSettings(); }); diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index deacff5abe7..6e3800021b4 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -2,6 +2,9 @@ import $ from 'jquery'; import axios from '~/lib/utils/axios_utils'; import flash from '../flash'; import { parseBoolean } from '~/lib/utils/common_utils'; +import TimezoneDropdown, { + formatTimezone, +} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown'; export default class Profile { constructor({ form } = {}) { @@ -10,6 +13,14 @@ export default class Profile { this.setRepoRadio(); this.bindEvents(); this.initAvatarGlCrop(); + + this.$inputEl = $('#user_timezone'); + + this.timezoneDropdown = new TimezoneDropdown({ + $inputEl: this.$inputEl, + $dropdownEl: $('.js-timezone-dropdown'), + displayFormat: selectedItem => formatTimezone(selectedItem), + }); } initAvatarGlCrop() { diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js index 40a873833e1..41e295387ae 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js +++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js @@ -1,3 +1,5 @@ +import { __ } from '~/locale'; + export default class ProtectedBranchAccessDropdown { constructor(options) { this.options = options; @@ -15,7 +17,7 @@ export default class ProtectedBranchAccessDropdown { if ($el.is('.is-active')) { return item.text; } - return 'Select'; + return __('Select'); }, clicked(options) { options.e.preventDefault(); diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js index 48343c8ba0a..16ecd5523d6 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_create.js +++ b/app/assets/javascripts/protected_branches/protected_branch_create.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import CreateItemDropdown from '../create_item_dropdown'; import AccessorUtilities from '../lib/utils/accessor'; +import { __ } from '~/locale'; export default class ProtectedBranchCreate { constructor() { @@ -35,7 +36,7 @@ export default class ProtectedBranchCreate { this.createItemDropdown = new CreateItemDropdown({ $dropdown: $protectedBranchDropdown, - defaultToggleLabel: 'Protected Branch', + defaultToggleLabel: __('Protected Branch'), fieldName: 'protected_branch[name]', onSelect: this.onSelectCallback, getData: ProtectedBranchCreate.getProtectedBranches, diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js index 5bc08f60d16..08d8c9919dd 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js @@ -1,6 +1,7 @@ import flash from '../flash'; import axios from '../lib/utils/axios_utils'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; +import { __ } from '~/locale'; export default class ProtectedBranchEdit { constructor(options) { @@ -68,7 +69,7 @@ export default class ProtectedBranchEdit { this.$allowedToPushDropdown.enable(); flash( - 'Failed to update branch!', + __('Failed to update branch!'), 'alert', document.querySelector('.js-protected-branches-list'), ); diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index c0659a0173a..10e2c8453e2 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -178,7 +178,7 @@ export default { /> <div ref="userStatusForm" class="form-group position-relative m-0"> <div class="input-group"> - <span class="input-group-btn"> + <span class="input-group-prepend"> <button ref="toggleEmojiMenuButton" v-gl-tooltip.bottom @@ -211,7 +211,7 @@ export default { @keyup.enter.prevent @click="hideEmojiMenu" /> - <span v-show="isDirty" class="input-group-btn"> + <span v-show="isDirty" class="input-group-append"> <button v-gl-tooltip.bottom :title="s__('SetStatusModal|Clear status')" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index f5a1ff2f6fd..f5fa68308bc 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -94,8 +94,8 @@ export default { </script> <template> - <div v-if="hasPipeline || hasCIError" class="ci-widget media js-ci-widget"> - <template v-if="hasCIError"> + <div class="ci-widget media js-ci-widget"> + <template v-if="!hasPipeline || hasCIError"> <div class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default" > diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue index c5a2aa1f2af..32783b85df4 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue @@ -1,8 +1,10 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; +import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; export default { - components: { Icon }, + components: { Icon, GlButton, GlLoadingIcon }, + directives: { 'gl-tooltip': GlTooltipDirective }, props: { canApply: { type: Boolean, @@ -21,7 +23,6 @@ export default { }, data() { return { - isAppliedSuccessfully: false, isApplying: false, }; }, @@ -47,14 +48,19 @@ export default { </a> </div> <span v-if="isApplied" class="badge badge-success">{{ __('Applied') }}</span> - <button - v-if="canApply" - type="button" - class="btn qa-apply-btn" + <div v-if="isApplying" class="d-flex align-items-center text-secondary"> + <gl-loading-icon class="d-flex-center mr-2" /> + <span>{{ __('Applying suggestion') }}</span> + </div> + <gl-button + v-else-if="canApply" + v-gl-tooltip.viewport="__('This also resolves the discussion')" + class="btn-inverted qa-apply-btn" :disabled="isApplying" + variant="success" @click="applySuggestion" > {{ __('Apply suggestion') }} - </button> + </gl-button> </div> </template> |