diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-16 15:15:41 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-16 15:15:41 +0300 |
commit | a32fd79d1e34ca4da1d5390c0aaf91d660e03fc8 (patch) | |
tree | 1fdbd979134478c9a2518a8a19a3404a7d1cc44c /app/assets/javascripts | |
parent | 884e3abdb08566b80afd73e9b0d5a7b6c5ac33bd (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
40 files changed, 436 insertions, 317 deletions
diff --git a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue index 146b647cb46..6567ce203bc 100644 --- a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue +++ b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue @@ -1,5 +1,12 @@ <script> -import { GlLoadingIcon, GlEmptyState, GlLink, GlIcon, GlAlert } from '@gitlab/ui'; +import { + GlLoadingIcon, + GlEmptyState, + GlLink, + GlIcon, + GlAlert, + GlTooltipDirective, +} from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { n__, s__, __ } from '~/locale'; import { formatDate, getDayDifference, isToday } from '~/lib/utils/datetime_utility'; @@ -16,10 +23,14 @@ export default { GlIcon, ActivityHistoryItem, }, + directives: { + GlTooltip: GlTooltipDirective, + }, i18n: { emptyText: s__( 'ClusterAgents|See Agent activity updates such as tokens created or revoked and clusters connected or not connected.', ), + emptyTooltip: s__('ClusterAgents|What is GitLab Agent activity?'), error: s__( 'ClusterAgents|An error occurred while retrieving GitLab Agent activity. Reload the page to try again.', ), @@ -150,12 +161,15 @@ export default { :svg-path="activityEmptyStateImage" :svg-height="150" > - <template #description> - <div> - <span>{{ $options.i18n.emptyText }}</span> - - <gl-link :href="$options.emptyHelpLink"><gl-icon name="question" :size="14" /></gl-link> - </div> + <template #description + >{{ $options.i18n.emptyText }} + <gl-link + v-gl-tooltip + :href="$options.emptyHelpLink" + :title="$options.i18n.emptyTooltip" + :aria-label="$options.i18n.emptyTooltip" + ><gl-icon name="question" :size="14" + /></gl-link> </template> </gl-empty-state> </div> diff --git a/app/assets/javascripts/clusters/agents/components/activity_history_item.vue b/app/assets/javascripts/clusters/agents/components/activity_history_item.vue index 39389c3b04b..7792d89a575 100644 --- a/app/assets/javascripts/clusters/agents/components/activity_history_item.vue +++ b/app/assets/javascripts/clusters/agents/components/activity_history_item.vue @@ -64,12 +64,12 @@ export default { <p class="gl-mt-2 gl-mb-0 gl-pb-2" :class="bodyClass"> <gl-sprintf :message="eventDetails.body"> <template #userName> - <strong>{{ eventDetails.user.name }}</strong> + <span class="gl-font-weight-bold">{{ eventDetails.user.name }}</span> <gl-link :href="eventDetails.user.webUrl">@{{ eventDetails.user.username }}</gl-link> </template> <template #strong="{ content }"> - <strong> {{ content }} </strong> + <span class="gl-font-weight-bold"> {{ content }} </span> </template> </gl-sprintf> <time-ago-tooltip :time="eventDetails.recordedAt" /> diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js index 177e3d6f9cc..84656bd41bb 100644 --- a/app/assets/javascripts/integrations/constants.js +++ b/app/assets/javascripts/integrations/constants.js @@ -1,6 +1,5 @@ import { s__, __ } from '~/locale'; -export const SAVE_INTEGRATION_EVENT = 'saveIntegration'; export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm'; export const integrationLevels = { diff --git a/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue b/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue index 89f7e3b7a89..bc6aa231a93 100644 --- a/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue +++ b/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue @@ -1,22 +1,17 @@ <script> import { GlModal } from '@gitlab/ui'; -import { mapGetters } from 'vuex'; import { __ } from '~/locale'; export default { components: { GlModal, }, + computed: { - ...mapGetters(['isDisabled']), primaryProps() { return { text: __('Save'), - attributes: [ - { variant: 'confirm' }, - { category: 'primary' }, - { disabled: this.isDisabled }, - ], + attributes: [{ variant: 'confirm' }, { category: 'primary' }], }; }, cancelProps() { diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue index 60a6d13ce69..e570a468944 100644 --- a/app/assets/javascripts/integrations/edit/components/integration_form.vue +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -4,7 +4,6 @@ import * as Sentry from '@sentry/browser'; import { mapState, mapActions, mapGetters } from 'vuex'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { - SAVE_INTEGRATION_EVENT, VALIDATE_INTEGRATION_FORM_EVENT, I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE, I18N_DEFAULT_ERROR_MESSAGE, @@ -54,12 +53,13 @@ export default { data() { return { integrationActive: false, - testingLoading: false, + isTesting: false, + isSaving: false, }; }, computed: { - ...mapGetters(['currentKey', 'propsSource', 'isDisabled']), - ...mapState(['defaultState', 'customState', 'override', 'isSaving', 'isResetting']), + ...mapGetters(['currentKey', 'propsSource']), + ...mapState(['defaultState', 'customState', 'override', 'isResetting']), isEditable() { return this.propsSource.editable; }, @@ -78,11 +78,8 @@ export default { showTestButton() { return this.propsSource.canTest; }, - disableSaveButton() { - return Boolean(this.isResetting || this.testingLoading); - }, - disableResetButton() { - return Boolean(this.isSaving || this.testingLoading); + disableButtons() { + return Boolean(this.isSaving || this.isResetting || this.isTesting); }, }, mounted() { @@ -90,21 +87,20 @@ export default { this.form = document.querySelector(this.formSelector); }, methods: { - ...mapActions([ - 'setOverride', - 'setIsSaving', - 'setIsResetting', - 'fetchResetIntegration', - 'requestJiraIssueTypes', - ]), + ...mapActions(['setOverride', 'fetchResetIntegration', 'requestJiraIssueTypes']), onSaveClick() { - this.setIsSaving(true); + this.isSaving = true; + + if (this.integrationActive && !this.form.checkValidity()) { + this.isSaving = false; + eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); + return; + } - const formValid = this.form.checkValidity() || this.integrationActive === false; - eventHub.$emit(SAVE_INTEGRATION_EVENT, formValid); + this.form.submit(); }, onTestClick() { - this.testingLoading = true; + this.isTesting = true; if (!this.form.checkValidity()) { eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); @@ -126,7 +122,7 @@ export default { Sentry.captureException(error); }) .finally(() => { - this.testingLoading = false; + this.isTesting = false; }); }, onResetClick() { @@ -211,7 +207,7 @@ export default { category="primary" variant="confirm" :loading="isSaving" - :disabled="disableSaveButton" + :disabled="disableButtons" data-qa-selector="save_changes_button" > {{ __('Save changes') }} @@ -224,7 +220,7 @@ export default { variant="confirm" type="submit" :loading="isSaving" - :disabled="disableSaveButton" + :disabled="disableButtons" data-testid="save-button" data-qa-selector="save_changes_button" @click.prevent="onSaveClick" @@ -236,8 +232,8 @@ export default { v-if="showTestButton" category="secondary" variant="confirm" - :loading="testingLoading" - :disabled="isDisabled" + :loading="isTesting" + :disabled="disableButtons" data-testid="test-button" @click.prevent="onTestClick" > @@ -250,7 +246,7 @@ export default { category="secondary" variant="confirm" :loading="isResetting" - :disabled="disableResetButton" + :disabled="disableButtons" data-testid="reset-button" > {{ __('Reset') }} diff --git a/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue b/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue index 9472a3eeafe..5a445235219 100644 --- a/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue +++ b/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue @@ -1,6 +1,5 @@ <script> import { GlModal } from '@gitlab/ui'; -import { mapGetters } from 'vuex'; import { __ } from '~/locale'; @@ -9,15 +8,10 @@ export default { GlModal, }, computed: { - ...mapGetters(['isDisabled']), primaryProps() { return { text: __('Reset'), - attributes: [ - { variant: 'warning' }, - { category: 'primary' }, - { disabled: this.isDisabled }, - ], + attributes: [{ variant: 'warning' }, { category: 'primary' }], }; }, cancelProps() { diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index 2e41d2914b7..9c9e3edbeb8 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -85,28 +85,31 @@ function parseDatasetToProps(data) { }; } -export default (el, defaultEl, formSelector) => { - if (!el) { +export default function initIntegrationSettingsForm(formSelector) { + const customSettingsEl = document.querySelector('.js-vue-integration-settings'); + const defaultSettingsEl = document.querySelector('.js-vue-default-integration-settings'); + + if (!customSettingsEl) { return null; } - const props = parseDatasetToProps(el.dataset); + const customSettingsProps = parseDatasetToProps(customSettingsEl.dataset); const initialState = { defaultState: null, - customState: props, + customState: customSettingsProps, }; - if (defaultEl) { - initialState.defaultState = Object.freeze(parseDatasetToProps(defaultEl.dataset)); + if (defaultSettingsEl) { + initialState.defaultState = Object.freeze(parseDatasetToProps(defaultSettingsEl.dataset)); } // Here, we capture the "helpHtml", so we can pass it to the Vue component // to position it where ever it wants. // Because this node is a _child_ of `el`, it will be removed when the Vue component is mounted, // so we don't need to manually remove it. - const helpHtml = el.querySelector('.js-integration-help-html')?.innerHTML; + const helpHtml = customSettingsEl.querySelector('.js-integration-help-html')?.innerHTML; return new Vue({ - el, + el: customSettingsEl, store: createStore(initialState), render(createElement) { return createElement(IntegrationForm, { @@ -117,4 +120,4 @@ export default (el, defaultEl, formSelector) => { }); }, }); -}; +} diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js index 6c1bfaa7858..97565a3a69c 100644 --- a/app/assets/javascripts/integrations/edit/store/actions.js +++ b/app/assets/javascripts/integrations/edit/store/actions.js @@ -10,7 +10,6 @@ import eventHub from '../event_hub'; 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 setIsResetting = ({ commit }, isResetting) => commit(types.SET_IS_RESETTING, isResetting); diff --git a/app/assets/javascripts/integrations/edit/store/getters.js b/app/assets/javascripts/integrations/edit/store/getters.js index 10d370de856..b79132128cc 100644 --- a/app/assets/javascripts/integrations/edit/store/getters.js +++ b/app/assets/javascripts/integrations/edit/store/getters.js @@ -1,7 +1,5 @@ export const isInheriting = (state) => (state.defaultState === null ? false : !state.override); -export const isDisabled = (state) => state.isSaving || state.isResetting; - export const propsSource = (state, getters) => getters.isInheriting ? state.defaultState : 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 dd4a79f6fe0..ddf6bef7554 100644 --- a/app/assets/javascripts/integrations/edit/store/mutation_types.js +++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js @@ -1,5 +1,4 @@ export const SET_OVERRIDE = 'SET_OVERRIDE'; -export const SET_IS_SAVING = 'SET_IS_SAVING'; export const SET_IS_RESETTING = 'SET_IS_RESETTING'; export const SET_IS_LOADING_JIRA_ISSUE_TYPES = 'SET_IS_LOADING_JIRA_ISSUE_TYPES'; diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js index f76081e62f0..e7e312ce650 100644 --- a/app/assets/javascripts/integrations/edit/store/mutations.js +++ b/app/assets/javascripts/integrations/edit/store/mutations.js @@ -4,9 +4,6 @@ export default { [types.SET_OVERRIDE](state, override) { state.override = override; }, - [types.SET_IS_SAVING](state, isSaving) { - state.isSaving = isSaving; - }, [types.SET_IS_RESETTING](state, isResetting) { state.isResetting = isResetting; }, diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js deleted file mode 100644 index 193bc1c4e9b..00000000000 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ /dev/null @@ -1,45 +0,0 @@ -import { delay } from 'lodash'; -import initForm from './edit'; -import eventHub from './edit/event_hub'; -import { SAVE_INTEGRATION_EVENT, VALIDATE_INTEGRATION_FORM_EVENT } from './constants'; - -export default class IntegrationSettingsForm { - constructor(formSelector) { - this.formSelector = formSelector; - this.$form = document.querySelector(formSelector); - - this.vue = null; - - // Form Metadata - this.testEndPoint = this.$form.dataset.testUrl; - } - - init() { - // Init Vue component - this.vue = initForm( - document.querySelector('.js-vue-integration-settings'), - document.querySelector('.js-vue-default-integration-settings'), - this.formSelector, - ); - eventHub.$on(SAVE_INTEGRATION_EVENT, (formValid) => { - this.saveIntegration(formValid); - }); - } - - saveIntegration(formValid) { - // Save Service if not active and check the following if active; - // 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 (formValid) { - delay(() => { - this.$form.submit(); - }, 100); - } else { - eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); - this.vue.$store.dispatch('setIsSaving', false); - } - } -} diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index 13e8943d655..eeaf865a35f 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -289,13 +289,11 @@ export default { window.addEventListener('beforeunload', this.handleBeforeUnloadEvent); - eventHub.$on('delete.issuable', this.deleteIssuable); eventHub.$on('update.issuable', this.updateIssuable); eventHub.$on('close.form', this.closeForm); eventHub.$on('open.form', this.openForm); }, beforeDestroy() { - eventHub.$off('delete.issuable', this.deleteIssuable); eventHub.$off('update.issuable', this.updateIssuable); eventHub.$off('close.form', this.closeForm); eventHub.$off('open.form', this.openForm); @@ -418,25 +416,6 @@ export default { }); }, - deleteIssuable(payload) { - return this.service - .deleteIssuable(payload) - .then((res) => res.data) - .then((data) => { - // Stop the poll so we don't get 404's with the issuable not existing - this.poll.stop(); - - visitUrl(data.web_url); - }) - .catch(() => { - createFlash({ - message: sprintf(__('Error deleting %{issuableType}'), { - issuableType: this.issuableType, - }), - }); - }); - }, - hideStickyHeader() { this.isStickyHeaderShowing = false; }, @@ -475,6 +454,7 @@ export default { <div> <div v-if="canUpdate && showForm"> <form-component + :endpoint="endpoint" :form-state="formState" :initial-description-text="initialDescriptionText" :can-destroy="canDestroy" diff --git a/app/assets/javascripts/issues/show/components/delete_issue_modal.vue b/app/assets/javascripts/issues/show/components/delete_issue_modal.vue new file mode 100644 index 00000000000..26862346b86 --- /dev/null +++ b/app/assets/javascripts/issues/show/components/delete_issue_modal.vue @@ -0,0 +1,71 @@ +<script> +import { GlModal } from '@gitlab/ui'; +import csrf from '~/lib/utils/csrf'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; +import { __, sprintf } from '~/locale'; + +export default { + actionCancel: { text: __('Cancel') }, + csrf, + components: { + GlModal, + }, + props: { + issuePath: { + type: String, + required: true, + }, + issueType: { + type: String, + required: true, + }, + modalId: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + }, + computed: { + actionPrimary() { + return { + attributes: { variant: 'danger' }, + text: this.title, + }; + }, + bodyText() { + return this.issueType.toLowerCase() === 'epic' + ? __('Delete this epic and all descendants?') + : sprintf(__('%{issuableType} will be removed! Are you sure?'), { + issuableType: capitalizeFirstCharacter(this.issueType), + }); + }, + }, + methods: { + submitForm() { + this.$emit('delete'); + this.$refs.form.submit(); + }, + }, +}; +</script> + +<template> + <gl-modal + :action-cancel="$options.actionCancel" + :action-primary="actionPrimary" + :modal-id="modalId" + size="sm" + :title="title" + @primary="submitForm" + > + <form ref="form" :action="issuePath" method="post"> + <input type="hidden" name="_method" value="delete" /> + <input type="hidden" name="authenticity_token" :value="$options.csrf.token" /> + <input type="hidden" name="destroy_confirm" value="true" /> + {{ bodyText }} + </form> + </gl-modal> +</template> diff --git a/app/assets/javascripts/issues/show/components/edit_actions.vue b/app/assets/javascripts/issues/show/components/edit_actions.vue index 5b7d232fde7..4daf6f2b61b 100644 --- a/app/assets/javascripts/issues/show/components/edit_actions.vue +++ b/app/assets/javascripts/issues/show/components/edit_actions.vue @@ -1,10 +1,12 @@ <script> -import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui'; +import { GlButton, GlModalDirective } from '@gitlab/ui'; import { uniqueId } from 'lodash'; import { __, sprintf } from '~/locale'; +import Tracking from '~/tracking'; import eventHub from '../event_hub'; import updateMixin from '../mixins/update'; import getIssueStateQuery from '../queries/get_issue_state.query.graphql'; +import DeleteIssueModal from './delete_issue_modal.vue'; const issuableTypes = { issue: __('Issue'), @@ -12,20 +14,26 @@ const issuableTypes = { incident: __('Incident'), }; +const trackingMixin = Tracking.mixin({ label: 'delete_issue' }); + export default { components: { + DeleteIssueModal, GlButton, - GlModal, }, directives: { GlModal: GlModalDirective, }, - mixins: [updateMixin], + mixins: [trackingMixin, updateMixin], props: { canDestroy: { type: Boolean, required: true, }, + endpoint: { + required: true, + type: String, + }, formState: { type: Object, required: true, @@ -65,27 +73,9 @@ export default { issuableType: this.typeToShow.toLowerCase(), }); }, - deleteIssuableModalText() { - return this.issuableType === 'epic' - ? __('Delete this epic and all descendants?') - : sprintf(__('%{issuableType} will be removed! Are you sure?'), { - issuableType: this.typeToShow, - }); - }, isSubmitEnabled() { return this.formState.title.trim() !== ''; }, - modalActionProps() { - return { - primary: { - text: this.deleteIssuableButtonText, - attributes: [{ variant: 'danger' }, { loading: this.deleteLoading }], - }, - cancel: { - text: __('Cancel'), - }, - }; - }, shouldShowDeleteButton() { return this.canDestroy && this.showDeleteButton; }, @@ -101,7 +91,7 @@ export default { }, deleteIssuable() { this.deleteLoading = true; - eventHub.$emit('delete.issuable', { destroy_confirm: true }); + eventHub.$emit('delete.issuable'); }, }, }; @@ -135,22 +125,17 @@ export default { variant="danger" class="qa-delete-button" data-testid="issuable-delete-button" + @click="track('click_button')" > {{ deleteIssuableButtonText }} </gl-button> - <gl-modal - ref="removeModal" + <delete-issue-modal + :issue-path="endpoint" + :issue-type="typeToShow" :modal-id="modalId" - size="sm" - :action-primary="modalActionProps.primary" - :action-cancel="modalActionProps.cancel" - @primary="deleteIssuable" - > - <template #modal-title>{{ deleteIssuableButtonText }}</template> - <div> - <p class="gl-mb-1">{{ deleteIssuableModalText }}</p> - </div> - </gl-modal> + :title="deleteIssuableButtonText" + @delete="deleteIssuable" + /> </div> </div> </template> diff --git a/app/assets/javascripts/issues/show/components/form.vue b/app/assets/javascripts/issues/show/components/form.vue index 783595028d5..6447ec85b4e 100644 --- a/app/assets/javascripts/issues/show/components/form.vue +++ b/app/assets/javascripts/issues/show/components/form.vue @@ -26,6 +26,10 @@ export default { type: Boolean, required: true, }, + endpoint: { + type: String, + required: true, + }, formState: { type: Object, required: true, @@ -213,6 +217,7 @@ export default { :enable-autocomplete="enableAutocomplete" /> <edit-actions + :endpoint="endpoint" :form-state="formState" :can-destroy="canDestroy" :show-delete-button="showDeleteButton" diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 75a5a4af949..700ef92a0f3 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -1,5 +1,13 @@ <script> -import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui'; +import { + GlButton, + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlLink, + GlModal, + GlModalDirective, +} from '@gitlab/ui'; import { mapActions, mapGetters, mapState } from 'vuex'; import createFlash, { FLASH_TYPES } from '~/flash'; import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants'; @@ -10,23 +18,21 @@ import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { visitUrl } from '~/lib/utils/url_utility'; import { s__, __, sprintf } from '~/locale'; import eventHub from '~/notes/event_hub'; +import Tracking from '~/tracking'; import promoteToEpicMutation from '../queries/promote_to_epic.mutation.graphql'; import updateIssueMutation from '../queries/update_issue.mutation.graphql'; +import DeleteIssueModal from './delete_issue_modal.vue'; + +const trackingMixin = Tracking.mixin({ label: 'delete_issue' }); export default { - components: { - GlButton, - GlDropdown, - GlDropdownItem, - GlLink, - GlModal, - }, actionCancel: { text: __('Cancel'), }, actionPrimary: { text: __('Yes, close issue'), }, + deleteModalId: 'delete-modal-id', i18n: { promoteErrorMessage: __( 'Something went wrong while promoting the issue to an epic. Please try again.', @@ -35,10 +41,26 @@ export default { 'The issue was successfully promoted to an epic. Redirecting to epic...', ), }, + components: { + DeleteIssueModal, + GlButton, + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlLink, + GlModal, + }, + directives: { + GlModal: GlModalDirective, + }, + mixins: [trackingMixin], inject: { canCreateIssue: { default: false, }, + canDestroyIssue: { + default: false, + }, canPromoteToEpic: { default: false, }, @@ -57,6 +79,9 @@ export default { isIssueAuthor: { default: false, }, + issuePath: { + default: '', + }, issueType: { default: IssuableType.Issue, }, @@ -92,6 +117,9 @@ export default { ? sprintf(__('Reopen %{issueType}'), { issueType: this.issueTypeText }) : sprintf(__('Close %{issueType}'), { issueType: this.issueTypeText }); }, + deleteButtonText() { + return sprintf(__('Delete %{issuableType}'), { issuableType: this.issueTypeText }); + }, qaSelector() { return this.isClosed ? 'reopen_issue_button' : 'close_issue_button'; }, @@ -141,8 +169,7 @@ export default { }) .then(({ data }) => { if (data.updateIssue.errors.length) { - createFlash({ message: data.updateIssue.errors.join('. ') }); - return; + throw new Error(); } const payload = { @@ -175,8 +202,7 @@ export default { }) .then(({ data }) => { if (data.promoteToEpic.errors.length) { - createFlash({ message: data.promoteToEpic.errors.join('; ') }); - return; + throw new Error(); } createFlash({ @@ -228,6 +254,16 @@ export default { > {{ __('Submit as spam') }} </gl-dropdown-item> + <template v-if="canDestroyIssue"> + <gl-dropdown-divider /> + <gl-dropdown-item + v-gl-modal="$options.deleteModalId" + variant="danger" + @click="track('click_dropdown')" + > + {{ deleteButtonText }} + </gl-dropdown-item> + </template> </gl-dropdown> <gl-button @@ -271,6 +307,16 @@ export default { > {{ __('Submit as spam') }} </gl-dropdown-item> + <template v-if="canDestroyIssue"> + <gl-dropdown-divider /> + <gl-dropdown-item + v-gl-modal="$options.deleteModalId" + variant="danger" + @click="track('click_dropdown')" + > + {{ deleteButtonText }} + </gl-dropdown-item> + </template> </gl-dropdown> <gl-modal @@ -288,5 +334,12 @@ export default { </li> </ul> </gl-modal> + + <delete-issue-modal + :issue-path="issuePath" + :issue-type="issueType" + :modal-id="$options.deleteModalId" + :title="deleteButtonText" + /> </div> </template> diff --git a/app/assets/javascripts/issues/show/incident.js b/app/assets/javascripts/issues/show/incident.js index 3aff2d9c54a..a260c31e1da 100644 --- a/app/assets/javascripts/issues/show/incident.js +++ b/app/assets/javascripts/issues/show/incident.js @@ -81,12 +81,14 @@ export function initIncidentHeaderActions(store) { store, provide: { canCreateIssue: parseBoolean(el.dataset.canCreateIncident), + canDestroyIssue: parseBoolean(el.dataset.canDestroyIssue), canPromoteToEpic: parseBoolean(el.dataset.canPromoteToEpic), canReopenIssue: parseBoolean(el.dataset.canReopenIssue), canReportSpam: parseBoolean(el.dataset.canReportSpam), canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue), iid: el.dataset.iid, isIssueAuthor: parseBoolean(el.dataset.isIssueAuthor), + issuePath: el.dataset.issuePath, issueType: el.dataset.issueType, newIssuePath: el.dataset.newIssuePath, projectPath: el.dataset.projectPath, diff --git a/app/assets/javascripts/issues/show/issue.js b/app/assets/javascripts/issues/show/issue.js index b3deb7571ee..60e90934af8 100644 --- a/app/assets/javascripts/issues/show/issue.js +++ b/app/assets/javascripts/issues/show/issue.js @@ -66,12 +66,14 @@ export function initIssueHeaderActions(store) { store, provide: { canCreateIssue: parseBoolean(el.dataset.canCreateIssue), + canDestroyIssue: parseBoolean(el.dataset.canDestroyIssue), canPromoteToEpic: parseBoolean(el.dataset.canPromoteToEpic), canReopenIssue: parseBoolean(el.dataset.canReopenIssue), canReportSpam: parseBoolean(el.dataset.canReportSpam), canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue), iid: el.dataset.iid, isIssueAuthor: parseBoolean(el.dataset.isIssueAuthor), + issuePath: el.dataset.issuePath, issueType: el.dataset.issueType, newIssuePath: el.dataset.newIssuePath, projectPath: el.dataset.projectPath, diff --git a/app/assets/javascripts/pages/admin/integrations/edit/index.js b/app/assets/javascripts/pages/admin/integrations/edit/index.js index 8002fa8bf78..8485b460261 100644 --- a/app/assets/javascripts/pages/admin/integrations/edit/index.js +++ b/app/assets/javascripts/pages/admin/integrations/edit/index.js @@ -1,15 +1,11 @@ -import IntegrationSettingsForm from '~/integrations/integration_settings_form'; +import initIntegrationSettingsForm from '~/integrations/edit'; import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; -function initIntegrations() { - const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring'); - const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); - integrationSettingsForm.init(); +initIntegrationSettingsForm('.js-integration-settings-form'); - if (prometheusSettingsWrapper) { - const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); - prometheusMetrics.loadActiveMetrics(); - } +const prometheusSettingsSelector = '.js-prometheus-metrics-monitoring'; +const prometheusSettingsWrapper = document.querySelector(prometheusSettingsSelector); +if (prometheusSettingsWrapper) { + const prometheusMetrics = new PrometheusMetrics(prometheusSettingsSelector); + prometheusMetrics.loadActiveMetrics(); } - -initIntegrations(); diff --git a/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js b/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js index a8698e10c57..8485b460261 100644 --- a/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js +++ b/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js @@ -1,11 +1,11 @@ -import IntegrationSettingsForm from '~/integrations/integration_settings_form'; +import initIntegrationSettingsForm from '~/integrations/edit'; import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; -const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring'); -const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); -integrationSettingsForm.init(); +initIntegrationSettingsForm('.js-integration-settings-form'); +const prometheusSettingsSelector = '.js-prometheus-metrics-monitoring'; +const prometheusSettingsWrapper = document.querySelector(prometheusSettingsSelector); if (prometheusSettingsWrapper) { - const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); + const prometheusMetrics = new PrometheusMetrics(prometheusSettingsSelector); prometheusMetrics.loadActiveMetrics(); } diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js index 03ffc323fc0..a2b18d86240 100644 --- a/app/assets/javascripts/pages/projects/services/edit/index.js +++ b/app/assets/javascripts/pages/projects/services/edit/index.js @@ -1,9 +1,8 @@ -import IntegrationSettingsForm from '~/integrations/integration_settings_form'; +import initIntegrationSettingsForm from '~/integrations/edit'; import PrometheusAlerts from '~/prometheus_alerts'; import CustomMetrics from '~/prometheus_metrics/custom_metrics'; -const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); -integrationSettingsForm.init(); +initIntegrationSettingsForm('.js-integration-settings-form'); const prometheusSettingsSelector = '.js-prometheus-metrics-monitoring'; const prometheusSettingsWrapper = document.querySelector(prometheusSettingsSelector); diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue index 0d28dd4e461..54c9688d88f 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue @@ -60,6 +60,9 @@ export default { apollo: { currentBranch: { query: getCurrentBranch, + update(data) { + return data.workBranches.current.name; + }, }, }, computed: { diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue index 7ac7bffc833..4f79a81d539 100644 --- a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue +++ b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue @@ -20,8 +20,8 @@ import { } from '~/pipeline_editor/constants'; import updateCurrentBranchMutation from '~/pipeline_editor/graphql/mutations/client/update_current_branch.mutation.graphql'; import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql'; -import getCurrentBranchQuery from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql'; -import getLastCommitBranchQuery from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql'; +import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql'; +import getLastCommitBranch from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql'; export default { i18n: { @@ -61,8 +61,8 @@ export default { }, data() { return { - branchSelected: null, availableBranches: [], + branchSelected: null, filteredBranches: [], isSearchingBranches: false, pageLimit: this.paginationLimit, @@ -93,15 +93,25 @@ export default { }, }, currentBranch: { - query: getCurrentBranchQuery, + query: getCurrentBranch, + update(data) { + return data.workBranches.current.name; + }, }, lastCommitBranch: { - query: getLastCommitBranchQuery, - result({ data: { lastCommitBranch } }) { - if (lastCommitBranch === '' || this.availableBranches.includes(lastCommitBranch)) { - return; + query: getLastCommitBranch, + update(data) { + return data.workBranches.lastCommit.name; + }, + result({ data }) { + if (data) { + const { name: lastCommitBranch } = data.workBranches.lastCommit; + if (lastCommitBranch === '' || this.availableBranches.includes(lastCommitBranch)) { + return; + } + + this.availableBranches.unshift(lastCommitBranch); } - this.availableBranches.unshift(lastCommitBranch); }, }, }, @@ -109,12 +119,12 @@ export default { branches() { return this.searchTerm.length > 0 ? this.filteredBranches : this.availableBranches; }, - isBranchesLoading() { - return this.$apollo.queries.availableBranches.loading || this.isSearchingBranches; - }, enableBranchSwitcher() { return this.branches.length > 0 || this.searchTerm.length > 0; }, + isBranchesLoading() { + return this.$apollo.queries.availableBranches.loading || this.isSearchingBranches; + }, }, watch: { shouldLoadNewBranch(flag) { diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue index 7d042e02052..16ad648afca 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -48,6 +48,9 @@ export default { apollo: { pipelineEtag: { query: getPipelineEtag, + update(data) { + return data.etags.pipeline; + }, }, pipeline: { context() { diff --git a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue index 798e337b493..833d784f940 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue @@ -43,6 +43,9 @@ export default { apollo: { appStatus: { query: getAppStatus, + update(data) { + return data.app.status; + }, }, }, computed: { diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue index bd4f8d6ebc3..3f50a1225d8 100644 --- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue +++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue @@ -91,6 +91,9 @@ export default { apollo: { appStatus: { query: getAppStatus, + update(data) { + return data.app.status; + }, }, }, computed: { diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql index 938f36c7d5c..0df8cafa3cb 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql @@ -1,3 +1,5 @@ query getAppStatus { - appStatus @client + app @client { + status + } } diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql index acd46013f5b..1f4f9d26f24 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql @@ -1,3 +1,7 @@ query getCurrentBranch { - currentBranch @client + workBranches @client { + current { + name + } + } } diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql index e8a32d728d5..a83129759de 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql @@ -1,3 +1,7 @@ query getLastCommitBranchQuery { - lastCommitBranch @client + workBranches @client { + lastCommit { + name + } + } } diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql index b9946a9e233..8df6e74a5d9 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql @@ -1,3 +1,5 @@ query getPipelineEtag { - pipelineEtag @client + etags @client { + pipeline + } } diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js index bb1f5a863cd..fa1c70c1994 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js +++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js @@ -1,7 +1,7 @@ import axios from '~/lib/utils/axios_utils'; import getAppStatus from './queries/client/app_status.query.graphql'; -import getCurrentBranchQuery from './queries/client/current_branch.query.graphql'; -import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql'; +import getCurrentBranch from './queries/client/current_branch.query.graphql'; +import getLastCommitBranch from './queries/client/last_commit_branch.query.graphql'; import getPipelineEtag from './queries/client/pipeline_etag.query.graphql'; export const resolvers = { @@ -35,25 +35,51 @@ export const resolvers = { updateAppStatus: (_, { appStatus }, { cache }) => { cache.writeQuery({ query: getAppStatus, - data: { appStatus }, + data: { + app: { + __typename: 'PipelineEditorApp', + status: appStatus, + }, + }, }); }, updateCurrentBranch: (_, { currentBranch }, { cache }) => { cache.writeQuery({ - query: getCurrentBranchQuery, - data: { currentBranch }, + query: getCurrentBranch, + data: { + workBranches: { + __typename: 'BranchList', + current: { + __typename: 'WorkBranch', + name: currentBranch, + }, + }, + }, }); }, updateLastCommitBranch: (_, { lastCommitBranch }, { cache }) => { cache.writeQuery({ - query: getLastCommitBranchQuery, - data: { lastCommitBranch }, + query: getLastCommitBranch, + data: { + workBranches: { + __typename: 'BranchList', + lastCommit: { + __typename: 'WorkBranch', + name: lastCommitBranch, + }, + }, + }, }); }, updatePipelineEtag: (_, { pipelineEtag }, { cache }) => { cache.writeQuery({ query: getPipelineEtag, - data: { pipelineEtag }, + data: { + etags: { + __typename: 'EtagValues', + pipeline: pipelineEtag, + }, + }, }); }, }, diff --git a/app/assets/javascripts/pipeline_editor/graphql/typedefs.graphql b/app/assets/javascripts/pipeline_editor/graphql/typedefs.graphql index f4f65262158..508ff22c46e 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/typedefs.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/typedefs.graphql @@ -1,7 +1,23 @@ -type BlobContent { - rawData: String! +type PipelineEditorApp { + status: String! +} + +type BranchList { + current: WorkBranch! + lastCommit: WorkBranch! +} + +type EtagValues { + pipeline: String! +} + +type WorkBranch { + name: String! + commit: String } extend type Query { - blobContent: BlobContent + app: PipelineEditorApp + etags: EtagValues + workBranches: BranchList } diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js index 299f593a19e..ee93e327b76 100644 --- a/app/assets/javascripts/pipeline_editor/index.js +++ b/app/assets/javascripts/pipeline_editor/index.js @@ -7,7 +7,7 @@ import { EDITOR_APP_STATUS_LOADING } from './constants'; import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants'; import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql'; import getAppStatus from './graphql/queries/client/app_status.query.graphql'; -import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql'; +import getLastCommitBranch from './graphql/queries/client/last_commit_branch.query.graphql'; import getPipelineEtag from './graphql/queries/client/pipeline_etag.query.graphql'; import { resolvers } from './graphql/resolvers'; import typeDefs from './graphql/typedefs.graphql'; @@ -68,28 +68,46 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { cache.writeQuery({ query: getAppStatus, data: { - appStatus: EDITOR_APP_STATUS_LOADING, + app: { + __typename: 'PipelineEditorApp', + status: EDITOR_APP_STATUS_LOADING, + }, }, }); cache.writeQuery({ query: getCurrentBranch, data: { - currentBranch: initialBranchName || defaultBranch, + workBranches: { + __typename: 'BranchList', + current: { + __typename: 'WorkBranch', + name: initialBranchName || defaultBranch, + }, + }, }, }); cache.writeQuery({ - query: getPipelineEtag, + query: getLastCommitBranch, data: { - pipelineEtag, + workBranches: { + __typename: 'BranchList', + lastCommit: { + __typename: 'WorkBranch', + name: '', + }, + }, }, }); cache.writeQuery({ - query: getLastCommitBranchQuery, + query: getPipelineEtag, data: { - lastCommitBranch: '', + etags: { + __typename: 'EtagValues', + pipeline: pipelineEtag, + }, }, }); diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 0c922979b14..e397054f06a 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -160,6 +160,9 @@ export default { }, appStatus: { query: getAppStatus, + update(data) { + return data.app.status; + }, }, commitSha: { query: getLatestCommitShaQuery, @@ -184,6 +187,9 @@ export default { }, currentBranch: { query: getCurrentBranch, + update(data) { + return data.workBranches.current.name; + }, }, starterTemplate: { query: getTemplate, diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue index a71097bb501..75d2b324623 100644 --- a/app/assets/javascripts/security_configuration/components/app.vue +++ b/app/assets/javascripts/security_configuration/components/app.vue @@ -4,7 +4,6 @@ import { __, s__ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; -import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql'; import AutoDevOpsAlert from './auto_dev_ops_alert.vue'; import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue'; import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants'; @@ -88,7 +87,6 @@ export default { return { autoDevopsEnabledAlertDismissedProjects: [], errorMessage: '', - securityTrainingProviders: [], }; }, computed: { @@ -110,11 +108,6 @@ export default { ); }, }, - apollo: { - securityTrainingProviders: { - query: securityTrainingProvidersQuery, - }, - }, methods: { dismissAutoDevopsEnabledAlert() { const dismissedProjects = new Set(this.autoDevopsEnabledAlertDismissedProjects); @@ -251,10 +244,7 @@ export default { > <section-layout :heading="$options.i18n.securityTraining"> <template #features> - <training-provider-list - :loading="$apollo.queries.securityTrainingProviders.loading" - :providers="securityTrainingProviders" - /> + <training-provider-list /> </template> </section-layout> </gl-tab> diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue index 4310ee3615e..509377a63e8 100644 --- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue +++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue @@ -1,5 +1,6 @@ <script> import { GlCard, GlToggle, GlLink, GlSkeletonLoader } from '@gitlab/ui'; +import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql'; export default { components: { @@ -8,15 +9,19 @@ export default { GlLink, GlSkeletonLoader, }, - props: { - providers: { - type: Array, - required: true, + apollo: { + securityTrainingProviders: { + query: securityTrainingProvidersQuery, }, - loading: { - type: Boolean, - required: false, - default: false, + }, + data() { + return { + securityTrainingProviders: [], + }; + }, + computed: { + isLoading() { + return this.$apollo.queries.securityTrainingProviders.loading; }, }, }; @@ -24,7 +29,7 @@ export default { <template> <div - v-if="loading" + v-if="isLoading" class="gl-bg-white gl-py-6 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100" > <gl-skeleton-loader :width="350" :height="44"> @@ -34,7 +39,11 @@ export default { </gl-skeleton-loader> </div> <ul v-else class="gl-list-style-none gl-m-0 gl-p-0"> - <li v-for="{ id, isEnabled, name, description, url } in providers" :key="id" class="gl-mb-6"> + <li + v-for="{ id, isEnabled, name, description, url } in securityTrainingProviders" + :key="id" + class="gl-mb-6" + > <gl-card> <div class="gl-display-flex"> <gl-toggle :value="isEnabled" :label="__('Training mode')" label-position="hidden" /> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue index aed5bc303ee..57ee816c4c7 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue @@ -1,10 +1,15 @@ <script> -import { GlLabel } from '@gitlab/ui'; +import { GlIcon, GlLabel, GlTooltipDirective } from '@gitlab/ui'; import { sortBy } from 'lodash'; import { isScopedLabel } from '~/lib/utils/common_utils'; +import { s__, sprintf } from '~/locale'; export default { + directives: { + GlTooltip: GlTooltipDirective, + }, components: { + GlIcon, GlLabel, }, inject: ['allowScopedLabels'], @@ -35,6 +40,23 @@ export default { sortedSelectedLabels() { return sortBy(this.selectedLabels, (label) => (isScopedLabel(label) ? 0 : 1)); }, + labelsList() { + const labelsString = this.selectedLabels.length + ? this.selectedLabels + .slice(0, 5) + .map((label) => label.title) + .join(', ') + : s__('LabelSelect|Labels'); + + if (this.selectedLabels.length > 5) { + return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), { + labelsString, + remainingLabelCount: this.selectedLabels.length - 5, + }); + } + + return labelsString; + }, }, methods: { labelFilterUrl(label) { @@ -48,6 +70,9 @@ export default { removeLabel(labelId) { this.$emit('onLabelRemove', labelId); }, + handleCollapsedClick() { + this.$emit('onCollapsedValueClick'); + }, }, }; </script> @@ -57,16 +82,30 @@ export default { :class="{ 'has-labels': selectedLabels.length, }" - class="hide-collapsed value issuable-show-labels js-value" + class="value issuable-show-labels js-value" data-testid="value-wrapper" > - <span v-if="!selectedLabels.length" class="text-secondary" data-testid="empty-placeholder"> + <div + v-gl-tooltip.left.viewport + :title="labelsList" + class="sidebar-collapsed-icon" + @click="handleCollapsedClick" + > + <gl-icon name="labels" /> + <span class="gl-font-base gl-line-height-24">{{ selectedLabels.length }}</span> + </div> + <span + v-if="!selectedLabels.length" + class="text-secondary hide-collapsed" + data-testid="empty-placeholder" + > <slot></slot> </span> <template v-else> <gl-label v-for="label in sortedSelectedLabels" :key="label.id" + class="hide-collapsed" data-qa-selector="selected_label_content" :data-qa-label-name="label.title" :title="label.title" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue deleted file mode 100644 index 122250d1ce7..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue +++ /dev/null @@ -1,55 +0,0 @@ -<script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { s__, sprintf } from '~/locale'; - -export default { - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { - GlIcon, - }, - props: { - labels: { - type: Array, - required: true, - }, - }, - computed: { - labelsList() { - const labelsString = this.labels.length - ? this.labels - .slice(0, 5) - .map((label) => label.title) - .join(', ') - : s__('LabelSelect|Labels'); - - if (this.labels.length > 5) { - return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), { - labelsString, - remainingLabelCount: this.labels.length - 5, - }); - } - - return labelsString; - }, - }, - methods: { - handleClick() { - this.$emit('onValueClick'); - }, - }, -}; -</script> - -<template> - <div - v-gl-tooltip.left.viewport - :title="labelsList" - class="sidebar-collapsed-icon" - @click="handleClick" - > - <gl-icon name="labels" /> - <span>{{ labels.length }}</span> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue index 2ef0d4fd9e6..3adda69b892 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue @@ -9,7 +9,6 @@ import { issuableLabelsQueries } from '~/sidebar/constants'; import { DEBOUNCE_DROPDOWN_DELAY, DropdownVariant } from './constants'; import DropdownContents from './dropdown_contents.vue'; import DropdownValue from './dropdown_value.vue'; -import DropdownValueCollapsed from './dropdown_value_collapsed.vue'; import { isDropdownVariantSidebar, isDropdownVariantStandalone, @@ -20,7 +19,6 @@ export default { components: { DropdownValue, DropdownContents, - DropdownValueCollapsed, SidebarEditableItem, }, inject: { @@ -294,11 +292,6 @@ export default { data-qa-selector="labels_block" > <template v-if="isDropdownVariantSidebar(variant)"> - <dropdown-value-collapsed - ref="dropdownButtonCollapsed" - :labels="issuableLabels" - @onValueClick="handleCollapsedValueClick" - /> <sidebar-editable-item ref="editable" :title="__('Labels')" @@ -314,6 +307,7 @@ export default { :labels-filter-base-path="labelsFilterBasePath" :labels-filter-param="labelsFilterParam" @onLabelRemove="handleLabelRemove" + @onCollapsedValueClick="handleCollapsedValueClick" > <slot></slot> </dropdown-value> |