diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-25 00:08:34 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-25 00:08:34 +0000 |
commit | 1e6730a4e32f6cbf4b84aa9fc13204778783f33c (patch) | |
tree | 6dd6c9ed98ec836432cf431397a4ef45dd78deb8 | |
parent | 95a48f11db963bc55ab918e3eb24f8576dca4a81 (diff) | |
download | gitlab-ce-1e6730a4e32f6cbf4b84aa9fc13204778783f33c.tar.gz |
Add latest changes from gitlab-org/gitlab@master
62 files changed, 602 insertions, 229 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index b6abb574e19..b1847fd9c6b 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -17,6 +17,14 @@ settings: webpack: config: './config/webpack.config.js' rules: + # BEGIN Disallow deprecated slot syntax + # TODO: Remove once + # https://gitlab.com/gitlab-org/frontend/eslint-plugin/-/issues/31 is closed + # and consumed by GitLab. + vue/no-deprecated-scope-attribute: error + vue/no-deprecated-slot-attribute: error + vue/no-deprecated-slot-scope-attribute: error + # END Disallow deprecated slot syntax import/no-commonjs: error import/no-default-export: off no-underscore-dangle: diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue index e6de724512f..96c3b8276ee 100644 --- a/app/assets/javascripts/batch_comments/components/draft_note.vue +++ b/app/assets/javascripts/batch_comments/components/draft_note.vue @@ -94,9 +94,11 @@ export default { @handleUpdateNote="update" @toggleResolveStatus="toggleResolveDiscussion(draft.id)" > - <strong slot="note-header-info" class="badge draft-pending-label gl-mr-2"> - {{ __('Pending') }} - </strong> + <template #note-header-info> + <strong class="badge draft-pending-label gl-mr-2"> + {{ __('Pending') }} + </strong> + </template> </noteable-note> </ul> diff --git a/app/assets/javascripts/contributors/components/contributors.vue b/app/assets/javascripts/contributors/components/contributors.vue index 25ce6500094..512f060e2ea 100644 --- a/app/assets/javascripts/contributors/components/contributors.vue +++ b/app/assets/javascripts/contributors/components/contributors.vue @@ -204,15 +204,16 @@ export default { <h4 class="gl-mb-2 gl-mt-5">{{ __('Commits to') }} {{ branch }}</h4> <span>{{ __('Excluding merge commits. Limited to 6,000 commits.') }}</span> <resizable-chart-container> - <gl-area-chart - slot-scope="{ width }" - class="gl-mb-5" - :width="width" - :data="masterChartData" - :option="masterChartOptions" - :height="masterChartHeight" - @created="onMasterChartCreated" - /> + <template #default="{ width }"> + <gl-area-chart + class="gl-mb-5" + :width="width" + :data="masterChartData" + :option="masterChartOptions" + :height="masterChartHeight" + @created="onMasterChartCreated" + /> + </template> </resizable-chart-container> <div class="row"> @@ -226,14 +227,15 @@ export default { {{ n__('%d commit', '%d commits', contributor.commits) }} ({{ contributor.email }}) </p> <resizable-chart-container> - <gl-area-chart - slot-scope="{ width }" - :width="width" - :data="contributor.dates" - :option="individualChartOptions" - :height="individualChartHeight" - @created="onIndividualChartCreated" - /> + <template #default="{ width }"> + <gl-area-chart + :width="width" + :data="contributor.dates" + :option="individualChartOptions" + :height="individualChartHeight" + @created="onIndividualChartCreated" + /> + </template> </resizable-chart-container> </div> </div> diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js index 88519d934cb..27ef47df8c8 100644 --- a/app/assets/javascripts/frequent_items/utils.js +++ b/app/assets/javascripts/frequent_items/utils.js @@ -35,13 +35,15 @@ export const getTopFrequentItems = (items) => { }; export const updateExistingFrequentItem = (frequentItem, item) => { - const accessedOverHourAgo = - Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / HOUR_IN_MS > 1; + // `frequentItem` comes from localStorage and it's possible it doesn't have a `lastAccessedOn` + const neverAccessed = !frequentItem.lastAccessedOn; + const shouldUpdate = + neverAccessed || Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / HOUR_IN_MS > 1; return { ...item, - frequency: accessedOverHourAgo ? frequentItem.frequency + 1 : frequentItem.frequency, - lastAccessedOn: accessedOverHourAgo ? Date.now() : frequentItem.lastAccessedOn, + frequency: shouldUpdate ? frequentItem.frequency + 1 : frequentItem.frequency, + lastAccessedOn: shouldUpdate ? Date.now() : frequentItem.lastAccessedOn, }; }; diff --git a/app/assets/javascripts/issuable_create/components/issuable_form.vue b/app/assets/javascripts/issuable_create/components/issuable_form.vue index 3cbd5620063..c216a05bdb0 100644 --- a/app/assets/javascripts/issuable_create/components/issuable_form.vue +++ b/app/assets/javascripts/issuable_create/components/issuable_form.vue @@ -72,16 +72,17 @@ export default { :show-suggest-popover="true" :textarea-value="issuableDescription" > - <textarea - id="issuable-description" - ref="textarea" - slot="textarea" - v-model="issuableDescription" - dir="auto" - class="note-textarea qa-issuable-form-description rspec-issuable-form-description js-gfm-input js-autosize markdown-area" - :aria-label="__('Description')" - :placeholder="__('Write a comment or drag your files here…')" - ></textarea> + <template #textarea> + <textarea + id="issuable-description" + ref="textarea" + v-model="issuableDescription" + dir="auto" + class="note-textarea qa-issuable-form-description rspec-issuable-form-description js-gfm-input js-autosize markdown-area" + :aria-label="__('Description')" + :placeholder="__('Write a comment or drag your files here…')" + ></textarea> + </template> </markdown-field> </div> </div> diff --git a/app/assets/javascripts/members/components/members_tabs.vue b/app/assets/javascripts/members/components/members_tabs.vue index a4bd8ffd708..7c21e33d892 100644 --- a/app/assets/javascripts/members/components/members_tabs.vue +++ b/app/assets/javascripts/members/components/members_tabs.vue @@ -116,7 +116,7 @@ export default { :title-link-attributes="tab.attrs" :query-param-value="tab.queryParamValue" > - <template slot="title"> + <template #title> <span>{{ tab.title }}</span> <gl-badge size="sm" class="gl-tab-counter-badge">{{ getTabCount(tab) }}</gl-badge> </template> diff --git a/app/assets/javascripts/milestones/components/milestone_combobox.vue b/app/assets/javascripts/milestones/components/milestone_combobox.vue index 1db2d10db20..7cdfdba6f27 100644 --- a/app/assets/javascripts/milestones/components/milestone_combobox.vue +++ b/app/assets/javascripts/milestones/components/milestone_combobox.vue @@ -171,7 +171,7 @@ export default { <template> <gl-dropdown v-bind="$attrs" class="milestone-combobox" @shown="focusSearchBox"> - <template slot="button-content"> + <template #button-content> <span data-testid="milestone-combobox-button-content" class="gl-flex-grow-1 text-muted">{{ selectedMilestonesLabel }}</span> diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue index 99008d047af..12f5e7efc96 100644 --- a/app/assets/javascripts/monitoring/components/charts/time_series.vue +++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue @@ -402,22 +402,20 @@ export default { @created="onChartCreated" @updated="onChartUpdated" > - <template v-if="tooltip.type === 'deployments'"> - <template slot="tooltip-title"> + <template #tooltip-title> + <template v-if="tooltip.type === 'deployments'"> {{ __('Deployed') }} </template> - <div slot="tooltip-content" class="d-flex align-items-center"> + <div v-else class="text-nowrap"> + {{ tooltip.title }} + </div> + </template> + <template #tooltip-content> + <div v-if="tooltip.type === 'deployments'" class="d-flex align-items-center"> <gl-icon name="commit" class="mr-2" /> <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link> </div> - </template> - <template v-else> - <template slot="tooltip-title"> - <div class="text-nowrap"> - {{ tooltip.title }} - </div> - </template> - <template slot="tooltip-content" :tooltip="tooltip"> + <template v-else> <div v-for="(content, key) in tooltip.content" :key="key" diff --git a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue index 94cfb562ce3..8e5a0b5cda2 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue @@ -138,10 +138,10 @@ export default { </script> <template> - <!-- + <!-- This component should be replaced with a variant developed as part of https://gitlab.com/gitlab-org/gitlab-ui/-/issues/936 - The variant will create a dropdown with an icon, no text and no caret + The variant will create a dropdown with an icon, no text and no caret --> <gl-dropdown v-gl-tooltip @@ -177,20 +177,22 @@ export default { @formValidation="setFormValidity" /> </form> - <div slot="modal-footer"> - <gl-button @click="hideAddMetricModal"> - {{ __('Cancel') }} - </gl-button> - <gl-button - v-track-event="getAddMetricTrackingOptions()" - data-testid="add-metric-modal-submit-button" - :disabled="!customMetricsFormIsValid" - variant="success" - @click="submitCustomMetricsForm" - > - {{ __('Save changes') }} - </gl-button> - </div> + <template #modal-footer> + <div> + <gl-button @click="hideAddMetricModal"> + {{ __('Cancel') }} + </gl-button> + <gl-button + v-track-event="getAddMetricTrackingOptions()" + data-testid="add-metric-modal-submit-button" + :disabled="!customMetricsFormIsValid" + variant="success" + @click="submitCustomMetricsForm" + > + {{ __('Save changes') }} + </gl-button> + </div> + </template> </gl-modal> </template> diff --git a/app/assets/javascripts/nav/components/top_nav_container_view.vue b/app/assets/javascripts/nav/components/top_nav_container_view.vue index 6f98f85ff90..36e4a278da9 100644 --- a/app/assets/javascripts/nav/components/top_nav_container_view.vue +++ b/app/assets/javascripts/nav/components/top_nav_container_view.vue @@ -20,6 +20,10 @@ export default { type: String, required: true, }, + currentItem: { + type: Object, + required: true, + }, containerClass: { type: String, required: false, @@ -43,6 +47,12 @@ export default { { id: 'secondary', menuItems: this.linksSecondary }, ].filter((x) => x.menuItems?.length); }, + currentItemTimestamped() { + return { + ...this.currentItem, + lastAccessedOn: Date.now(), + }; + }, }, mounted() { // For historic reasons, the frequent-items-app component requires this too start up. @@ -62,7 +72,7 @@ export default { > <div class="frequent-items-dropdown-content gl-w-full! gl-pt-0!"> <vuex-module-provider :vuex-module="frequentItemsVuexModule"> - <frequent-items-app v-bind="$attrs" /> + <frequent-items-app :current-item="currentItemTimestamped" v-bind="$attrs" /> </vuex-module-provider> </div> </div> diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue index dfe2763d8bd..0892276ff3b 100644 --- a/app/assets/javascripts/notes/components/discussion_notes.vue +++ b/app/assets/javascripts/notes/components/discussion_notes.vue @@ -130,15 +130,18 @@ export default { @handleDeleteNote="$emit('deleteNote')" @startReplying="$emit('startReplying')" > - <note-edited-text - v-if="discussion.resolved" - slot="discussion-resolved-text" - :edited-at="discussion.resolved_at" - :edited-by="discussion.resolved_by" - :action-text="resolvedText" - class-name="discussion-headline-light js-discussion-headline discussion-resolved-text" - /> - <slot slot="avatar-badge" name="avatar-badge"></slot> + <template #discussion-resolved-text> + <note-edited-text + v-if="discussion.resolved" + :edited-at="discussion.resolved_at" + :edited-by="discussion.resolved_by" + :action-text="resolvedText" + class-name="discussion-headline-light js-discussion-headline discussion-resolved-text" + /> + </template> + <template #avatar-badge> + <slot name="avatar-badge"></slot> + </template> </component> <discussion-notes-replies-wrapper :is-diff-discussion="discussion.diff_discussion"> <toggle-replies-widget @@ -175,7 +178,9 @@ export default { :discussion-resolve-path="discussion.resolve_path" @handleDeleteNote="$emit('deleteNote')" > - <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot> + <template #avatar-badge> + <slot v-if="index === 0" name="avatar-badge"></slot> + </template> </component> <slot :show-replies="isExpanded || !hasReplies" name="footer"></slot> </template> diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index a66b8be652f..c503fb561ee 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -265,7 +265,9 @@ export default { @startReplying="showReplyForm" @deleteNote="deleteNoteHandler" > - <slot slot="avatar-badge" name="avatar-badge"></slot> + <template #avatar-badge> + <slot name="avatar-badge"></slot> + </template> <template #footer="{ showReplies }"> <draft-note v-if="showDraft(discussion.reply_id)" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 83942499f4e..c91ae1ed14e 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -392,7 +392,9 @@ export default { :img-alt="author.name" :img-size="40" > - <slot slot="avatar-badge" name="avatar-badge"></slot> + <template #avatar-badge> + <slot name="avatar-badge"></slot> + </template> </user-avatar-link> </div> <div class="timeline-content"> @@ -403,7 +405,9 @@ export default { :note-id="note.id" :is-confidential="note.confidential" > - <slot slot="note-header-info" name="note-header-info"></slot> + <template #note-header-info> + <slot name="note-header-info"></slot> + </template> <span v-if="commit" v-safe-html="actionText"></span> <span v-else-if="note.created_at" class="d-none d-sm-inline">·</span> </note-header> diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue index 75c3b6d564c..ccdcd3c94e7 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue @@ -301,7 +301,7 @@ export default { :state="form.fields.namespace.state" required > - <template slot="first"> + <template #first> <option :value="null" disabled>{{ s__('ForkProject|Select a namespace') }}</option> </template> <option v-for="namespace in namespaces" :key="namespace.id" :value="namespace"> diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue index aaa9bb906b2..e708cd32fff 100644 --- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue +++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue @@ -85,28 +85,29 @@ export default { :action-cancel="$options.cancelProps" @primary="onSubmit" > - <div slot="modal-title" class="modal-title-with-label"> - <gl-sprintf - :message=" - s__( - 'Labels|%{spanStart}Promote label%{spanEnd} %{labelTitle} %{spanStart}to Group Label?%{spanEnd}', - ) - " - > - <template #labelTitle> - <span - class="label color-label" - :style="`background-color: ${labelColor}; color: ${labelTextColor};`" - > - {{ labelTitle }} - </span> - </template> - <template #span="{ content }" - ><span>{{ content }}</span></template + <template #modal-title> + <div class="modal-title-with-label"> + <gl-sprintf + :message=" + s__( + 'Labels|%{spanStart}Promote label%{spanEnd} %{labelTitle} %{spanStart}to Group Label?%{spanEnd}', + ) + " > - </gl-sprintf> - </div> - + <template #labelTitle> + <span + class="label color-label" + :style="`background-color: ${labelColor}; color: ${labelTextColor};`" + > + {{ labelTitle }} + </span> + </template> + <template #span="{ content }" + ><span>{{ content }}</span></template + > + </gl-sprintf> + </div> + </template> {{ text }} </gl-modal> </template> diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue index fb00f58abae..4c083ed5496 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue @@ -1,5 +1,5 @@ <script> -import { GlAlert } from '@gitlab/ui'; +import { GlAlert, GlSafeHtmlDirective } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import { __, sprintf } from '~/locale'; import ServiceDeskSetting from './service_desk_setting.vue'; @@ -9,6 +9,9 @@ export default { GlAlert, ServiceDeskSetting, }, + directives: { + SafeHtml: GlSafeHtmlDirective, + }, inject: { initialIsEnabled: { default: false, @@ -121,7 +124,7 @@ export default { <template> <div> <gl-alert v-if="isAlertShowing" class="mb-3" :variant="alertVariant" @dismiss="onDismiss"> - {{ alertMessage }} + <span v-safe-html="alertMessage"></span> </gl-alert> <service-desk-setting :is-enabled="isEnabled" diff --git a/app/assets/javascripts/static_site_editor/rich_content_editor/modals/insert_video_modal.vue b/app/assets/javascripts/static_site_editor/rich_content_editor/modals/insert_video_modal.vue index 99bb2080610..5ce2c17f8de 100644 --- a/app/assets/javascripts/static_site_editor/rich_content_editor/modals/insert_video_modal.vue +++ b/app/assets/javascripts/static_site_editor/rich_content_editor/modals/insert_video_modal.vue @@ -81,11 +81,13 @@ export default { :invalid-feedback="urlError" > <gl-form-input id="video-modal-url-input" ref="urlInput" v-model="url" /> - <gl-sprintf slot="description" :message="description" class="text-gl-muted"> - <template #id> - <strong>{{ __('0t1DgySidms') }}</strong> - </template> - </gl-sprintf> + <template #description> + <gl-sprintf :message="description" class="text-gl-muted"> + <template #id> + <strong>{{ __('0t1DgySidms') }}</strong> + </template> + </gl-sprintf> + </template> </gl-form-group> </gl-modal> </template> diff --git a/app/assets/javascripts/terraform/components/terraform_list.vue b/app/assets/javascripts/terraform/components/terraform_list.vue index a18f33ebb1f..7eb79120fb8 100644 --- a/app/assets/javascripts/terraform/components/terraform_list.vue +++ b/app/assets/javascripts/terraform/components/terraform_list.vue @@ -98,7 +98,7 @@ export default { <section> <gl-tabs> <gl-tab> - <template slot="title"> + <template #title> <p class="gl-m-0"> {{ s__('Terraform|States') }} <gl-badge v-if="statesCount">{{ statesCount }}</gl-badge> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 07de525b1fa..7ab8da93a57 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -629,11 +629,9 @@ export default { input-id="squash-message-edit" squash > - <commit-message-dropdown - slot="header" - v-model="squashCommitMessage" - :commits="commits" - /> + <template #header> + <commit-message-dropdown v-model="squashCommitMessage" :commits="commits" /> + </template> </commit-edit> <commit-edit v-if="shouldShowMergeEdit" @@ -641,14 +639,16 @@ export default { :label="__('Merge commit message')" input-id="merge-message-edit" > - <label slot="checkbox"> - <input - id="include-description" - type="checkbox" - @change="updateMergeCommitMessage($event.target.checked)" - /> - {{ __('Include merge request description') }} - </label> + <template #checkbox> + <label> + <input + id="include-description" + type="checkbox" + @change="updateMergeCommitMessage($event.target.checked)" + /> + {{ __('Include merge request description') }} + </label> + </template> </commit-edit> </ul> </commits-header> diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue index 13472b48e84..bab13fe7c75 100644 --- a/app/assets/javascripts/vue_shared/components/actions_button.vue +++ b/app/assets/javascripts/vue_shared/components/actions_button.vue @@ -68,7 +68,7 @@ export default { split @click="handleClick(selectedAction, $event)" > - <template slot="button-content"> + <template #button-content> <span class="gl-new-dropdown-button-text" v-bind="selectedAction.attrs"> {{ selectedAction.text }} </span> diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue index 2552236a073..fb7105bd416 100644 --- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue +++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue @@ -28,18 +28,23 @@ export default { <slot></slot> </p> <resizable-chart-container> - <gl-area-chart - slot-scope="{ width }" - v-bind="$attrs" - :width="width" - :height="$options.chartContainerHeight" - :data="chartData" - :include-legend-avg-max="false" - :option="areaChartOptions" - > - <slot slot="tooltip-title" name="tooltip-title"></slot> - <slot slot="tooltip-content" name="tooltip-content"></slot> - </gl-area-chart> + <template #default="{ width }"> + <gl-area-chart + v-bind="$attrs" + :width="width" + :height="$options.chartContainerHeight" + :data="chartData" + :include-legend-avg-max="false" + :option="areaChartOptions" + > + <template #tooltip-title> + <slot name="tooltip-title"></slot> + </template> + <template #tooltip-content> + <slot name="tooltip-content"></slot> + </template> + </gl-area-chart> + </template> </resizable-chart-container> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue index f4fd57e4cdc..0575d7f6404 100644 --- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue +++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue @@ -46,9 +46,12 @@ export default { :area-chart-options="chartOptions" > {{ dateRange }} - - <slot slot="tooltip-title" name="tooltip-title"></slot> - <slot slot="tooltip-content" name="tooltip-content"></slot> + <template #tooltip-title> + <slot name="tooltip-title"></slot> + </template> + <template #tooltip-content> + <slot name="tooltip-content"></slot> + </template> </ci-cd-analytics-area-chart> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/expand_button.vue b/app/assets/javascripts/vue_shared/components/expand_button.vue index 546ee56355f..0b92c947fc7 100644 --- a/app/assets/javascripts/vue_shared/components/expand_button.vue +++ b/app/assets/javascripts/vue_shared/components/expand_button.vue @@ -7,7 +7,7 @@ import { __ } from '~/locale'; * * @example * <expand-button> - * <template slot="expanded"> + * <template #expanded> * Text goes here. * </template> * </expand-button> diff --git a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue index 96d99faa952..dd0c0358ef6 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue @@ -74,6 +74,8 @@ export default { @hidden="syncHide" > <slot></slot> - <slot slot="modal-footer" name="modal-footer" :ok="ok" :cancel="cancel"></slot> + <template #modal-footer> + <slot name="modal-footer" :ok="ok" :cancel="cancel"></slot> + </template> </gl-modal> </template> diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue index 149909d263e..c3d861d74bc 100644 --- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue @@ -111,7 +111,7 @@ export default { <div class="note-header"> <note-header :author="note.author" :created-at="note.created_at" :note-id="note.id"> <span v-safe-html="actionTextHtml"></span> - <template v-if="canSeeDescriptionVersion" slot="extra-controls"> + <template v-if="canSeeDescriptionVersion" #extra-controls> · <gl-button variant="link" diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss index c25b5b53070..6902a67fbb7 100644 --- a/app/assets/stylesheets/startup/startup-signin.scss +++ b/app/assets/stylesheets/startup/startup-signin.scss @@ -796,9 +796,15 @@ svg { .gl-display-flex { display: flex; } +.gl-display-block { + display: block; +} .gl-align-items-center { align-items: center; } +.gl-w-full { + width: 100%; +} .gl-p-2 { padding: 0.25rem; } diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 965e1806416..1d70698fb76 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -85,10 +85,6 @@ padding-bottom: $gl-spacing-scale-8; } -.gl-pt-11 { - padding-top: $gl-spacing-scale-11; -} - .gl-transition-property-stroke-opacity { transition-property: stroke-opacity; } @@ -121,6 +117,20 @@ } } +// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1462 +.gl-md-mt-11 { + @media (min-width: $breakpoint-md) { + margin-top: $gl-spacing-scale-11; + } +} + +// Same as above +.gl-md-pt-11 { + @media (min-width: $breakpoint-md) { + padding-top: $gl-spacing-scale-11; + } +} + // This is used to help prevent issues with margin collapsing. // See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing. .gl-force-block-formatting-context::after { @@ -142,6 +152,13 @@ } } +// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1463 +.gl-xs-mt-6 { + @media (max-width: $breakpoint-sm) { + margin-top: $gl-spacing-scale-6; + } +} + // Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1165 .gl-xs-mb-4 { @media (max-width: $breakpoint-sm) { @@ -156,6 +173,13 @@ } } +// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1463 +.gl-xs-pt-6 { + @media (max-width: $breakpoint-sm) { + padding-top: $gl-spacing-scale-6; + } +} + // Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168 .gl-sm-pr-3 { @media (min-width: $breakpoint-sm) { diff --git a/app/graphql/mutations/packages/destroy.rb b/app/graphql/mutations/packages/destroy.rb new file mode 100644 index 00000000000..979a54da6bd --- /dev/null +++ b/app/graphql/mutations/packages/destroy.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Mutations + module Packages + class Destroy < ::Mutations::BaseMutation + graphql_name 'DestroyPackage' + + authorize :destroy_package + + argument :id, + ::Types::GlobalIDType[::Packages::Package], + required: true, + description: 'ID of the Package.' + + def resolve(id:) + package = authorized_find!(id: id) + + result = ::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute + + errors = result.error? ? Array.wrap(result[:message]) : [] + + { + errors: errors + } + end + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Packages::Package].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 6b1146f8f09..d321efecb1b 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -104,6 +104,7 @@ module Types mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, feature_flag: :runner_graphql_query mount_mutation Mutations::Namespace::PackageSettings::Update mount_mutation Mutations::UserCallouts::Create + mount_mutation Mutations::Packages::Destroy end end diff --git a/app/models/service_desk_setting.rb b/app/models/service_desk_setting.rb index c5203354b9d..1c854cc9941 100644 --- a/app/models/service_desk_setting.rb +++ b/app/models/service_desk_setting.rb @@ -8,7 +8,10 @@ class ServiceDeskSetting < ApplicationRecord validate :valid_issue_template validate :valid_project_key validates :outgoing_name, length: { maximum: 255 }, allow_blank: true - validates :project_key, length: { maximum: 255 }, allow_blank: true, format: { with: /\A[a-z0-9_]+\z/ } + validates :project_key, + length: { maximum: 255 }, + allow_blank: true, + format: { with: /\A[a-z0-9_]+\z/, message: -> (setting, data) { _("can contain only lowercase letters, digits, and '_'.") } } scope :with_project_key, ->(key) { where(project_key: key) } diff --git a/app/services/packages/destroy_package_service.rb b/app/services/packages/destroy_package_service.rb new file mode 100644 index 00000000000..697f1fa3ac8 --- /dev/null +++ b/app/services/packages/destroy_package_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Packages + class DestroyPackageService < BaseContainerService + alias_method :package, :container + + def execute + return service_response_error("You don't have access to this package", 403) unless user_can_delete_package? + + package.destroy! + + package.sync_maven_metadata(current_user) + + service_response_success('Package was successfully deleted') + rescue StandardError + service_response_error('Failed to remove the package', 400) + end + + private + + def service_response_error(message, http_status) + ServiceResponse.error(message: message, http_status: http_status) + end + + def service_response_success(message) + ServiceResponse.success(message: message) + end + + def user_can_delete_package? + can?(current_user, :destroy_package, package.project) + end + end +end diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 09b7f247450..a313ad7d23c 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -61,7 +61,7 @@ - if show_recaptcha_sign_up? = recaptcha_tags nonce: content_security_policy_nonce .submit-container - = f.submit button_text, class: 'btn gl-button btn-confirm', data: { qa_selector: 'new_user_register_button' } + = f.submit button_text, class: 'btn gl-button btn-confirm gl-display-block gl-w-full', data: { qa_selector: 'new_user_register_button' } = render 'devise/shared/terms_of_service_notice', button_text: button_text - if show_omniauth_providers && omniauth_providers_placement == :bottom = render 'devise/shared/signup_omniauth_providers' diff --git a/app/views/layouts/minimal.html.haml b/app/views/layouts/minimal.html.haml index c9b208de477..ec909fcc279 100644 --- a/app/views/layouts/minimal.html.haml +++ b/app/views/layouts/minimal.html.haml @@ -8,10 +8,11 @@ = render 'peek/bar' = render "layouts/header/empty" .layout-page - .content-wrapper.content-wrapper-margin.gl-pt-11 + .content-wrapper.content-wrapper-margin.gl-md-pt-11.gl-xs-pt-6 .alert-wrapper.gl-force-block-formatting-context = render "layouts/broadcast" .limit-container-width{ class: container_class } %main#content-body.content + = render "layouts/flash" unless @hide_flash = yield = footer_message diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 6cb2c435a30..6de50d48721 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -2,10 +2,10 @@ - default_ref = params[:ref] || @project.default_branch - if @error - .gl-alert.gl-alert-danger - = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - %button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') } - = sprite_icon('close', size: 16, css_class: 'gl-icon') + = render 'shared/global_alert', + variant: :danger, + close_button_class: 'js-close', + is_contained: true do .gl-alert-body = @error %h3.page-title diff --git a/app/views/projects/issues/_alert_moved_from_service_desk.html.haml b/app/views/projects/issues/_alert_moved_from_service_desk.html.haml index 9b142b08574..662270fb8e1 100644 --- a/app/views/projects/issues/_alert_moved_from_service_desk.html.haml +++ b/app/views/projects/issues/_alert_moved_from_service_desk.html.haml @@ -2,9 +2,10 @@ - service_desk_link_url = help_page_path('user/project/service_desk') - service_desk_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: service_desk_link_url } -.hide.gl-alert.gl-alert-warning.js-alert-moved-from-service-desk-warning.gl-mt-5{ role: 'alert' } - = sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - %button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') } - = sprite_icon('close', css_class: 'gl-icon') += render 'shared/global_alert', + variant: :warning, + is_contained: true, + close_button_class: 'js-close', + alert_class: 'hide js-alert-moved-from-service-desk-warning gl-mt-5' do .gl-alert-body.gl-mr-3 = s_('This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity.').html_safe % { service_desk_link_start: service_desk_link_start, service_desk_link_end: '</a>'.html_safe } diff --git a/app/views/registrations/experience_levels/show.html.haml b/app/views/registrations/experience_levels/show.html.haml index f878245a48c..16e59757147 100644 --- a/app/views/registrations/experience_levels/show.html.haml +++ b/app/views/registrations/experience_levels/show.html.haml @@ -1,4 +1,5 @@ - page_title _('What’s your experience level?') +- @hide_flash = true .gl-display-flex.gl-flex-direction-column.gl-align-items-center = image_tag 'learn-gitlab-avatar.jpg', width: '90' diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e7ee6d2b01d..420744d2f73 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1835,6 +1835,24 @@ Input type: `DestroyNoteInput` | <a id="mutationdestroynoteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationdestroynotenote"></a>`note` | [`Note`](#note) | The note after mutation. | +### `Mutation.destroyPackage` + +Input type: `DestroyPackageInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationdestroypackageclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationdestroypackageid"></a>`id` | [`PackagesPackageID!`](#packagespackageid) | ID of the Package. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationdestroypackageclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationdestroypackageerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.destroySnippet` Input type: `DestroySnippetInput` diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 4a96efc3d72..27311d4b55e 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -702,6 +702,7 @@ dast: DAST_AUTH_REPORT: "true" artifacts: paths: [gl-dast-debug-auth-report.html] + when: always ``` ### Available CI/CD variables diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md index 27642a9bd5d..876f1e9ed79 100644 --- a/doc/user/project/merge_requests/code_quality.md +++ b/doc/user/project/merge_requests/code_quality.md @@ -342,13 +342,13 @@ do this: The Code Quality report artifact JSON file must contain an array of objects with the following properties: -| Name | Description | -| ---------------------- | -------------------------------------------------------------------------------------- | -| `description` | A description of the code quality violation. | -| `fingerprint` | A unique fingerprint to identify the code quality violation. For example, an MD5 hash. | -| `severity` | A severity string (can be `info`, `minor`, `major`, `critical`, or `blocker`). | -| `location.path` | The relative path to the file containing the code quality violation. | -| `location.lines.begin` | The line on which the code quality violation occurred. | +| Name | Description | +| ---------------------- | ----------------------------------------------------------------------------------------- | +| `description` | A description of the code quality violation. | +| `fingerprint` | A unique fingerprint to identify the code quality violation. For example, an MD5 hash. | +| `severity` | A severity string (can be `info`, `minor`, `major`, `critical`, or `blocker`). | +| `location.path` | The relative path to the file containing the code quality violation. | +| `location.lines.begin` or `location.positions.begin.line` | The line on which the code quality violation occurred. | Example: diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 276cbe50e42..c3918f71427 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -71,9 +71,7 @@ module API .new(user_project, params[:package_id]).execute destroy_conditionally!(package) do |package| - if package.destroy - package.sync_maven_metadata(current_user) - end + ::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 81ec7baa924..acb0bfafedd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -37898,6 +37898,9 @@ msgstr "" msgid "can contain only letters of the Base64 alphabet (RFC4648) with the addition of '@', ':' and '.'" msgstr "" +msgid "can contain only lowercase letters, digits, and '_'." +msgstr "" + msgid "can only be changed by a group admin." msgstr "" diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js index c2d488a465e..5d22823e974 100644 --- a/spec/frontend/batch_comments/components/draft_note_spec.js +++ b/spec/frontend/batch_comments/components/draft_note_spec.js @@ -1,5 +1,6 @@ import { getByRole } from '@testing-library/dom'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; import DraftNote from '~/batch_comments/components/draft_note.vue'; import { createStore } from '~/batch_comments/stores'; import NoteableNote from '~/notes/components/noteable_note.vue'; @@ -8,6 +9,14 @@ import { createDraft } from '../mock_data'; const localVue = createLocalVue(); +const NoteableNoteStub = stubComponent(NoteableNote, { + template: ` + <div> + <slot name="note-header-info">Test</slot> + </div> + `, +}); + describe('Batch comments draft note component', () => { let store; let wrapper; @@ -26,6 +35,9 @@ describe('Batch comments draft note component', () => { store, propsData, localVue, + stubs: { + NoteableNote: NoteableNoteStub, + }, }); jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation(); diff --git a/spec/frontend/frequent_items/utils_spec.js b/spec/frontend/frequent_items/utils_spec.js index a7ab18b0d10..8c3841558f4 100644 --- a/spec/frontend/frequent_items/utils_spec.js +++ b/spec/frontend/frequent_items/utils_spec.js @@ -66,35 +66,36 @@ describe('Frequent Items utils spec', () => { }); describe('updateExistingFrequentItem', () => { - let mockedProject; - - beforeEach(() => { - mockedProject = { - ...mockProject, - frequency: 1, - lastAccessedOn: 1497979281815, - }; + const LAST_ACCESSED = 1497979281815; + const WITHIN_AN_HOUR = LAST_ACCESSED + HOUR_IN_MS; + const OVER_AN_HOUR = WITHIN_AN_HOUR + 1; + const EXISTING_ITEM = Object.freeze({ + ...mockProject, + frequency: 1, + lastAccessedOn: 1497979281815, }); - it('updates item if accessed over an hour ago', () => { - const newTimestamp = Date.now() + HOUR_IN_MS + 1; + it.each` + desc | existingProps | newProps | expected + ${'updates item if accessed over an hour ago'} | ${{}} | ${{ lastAccessedOn: OVER_AN_HOUR }} | ${{ lastAccessedOn: Date.now(), frequency: 2 }} + ${'does not update is accessed with an hour'} | ${{}} | ${{ lastAccessedOn: WITHIN_AN_HOUR }} | ${{ lastAccessedOn: EXISTING_ITEM.lastAccessedOn, frequency: 1 }} + ${'updates if lastAccessedOn not found'} | ${{ lastAccessedOn: undefined }} | ${{ lastAccessedOn: WITHIN_AN_HOUR }} | ${{ lastAccessedOn: Date.now(), frequency: 2 }} + `('$desc', ({ existingProps, newProps, expected }) => { const newItem = { - ...mockedProject, - lastAccessedOn: newTimestamp, + ...EXISTING_ITEM, + ...newProps, }; - const result = updateExistingFrequentItem(mockedProject, newItem); - - expect(result.frequency).toBe(mockedProject.frequency + 1); - }); - - it('does not update item if accessed within the hour', () => { - const newItem = { - ...mockedProject, - lastAccessedOn: mockedProject.lastAccessedOn + HOUR_IN_MS, + const existingItem = { + ...EXISTING_ITEM, + ...existingProps, }; - const result = updateExistingFrequentItem(mockedProject, newItem); - expect(result.frequency).toBe(mockedProject.frequency); + const result = updateExistingFrequentItem(existingItem, newItem); + + expect(result).toEqual({ + ...newItem, + ...expected, + }); }); }); diff --git a/spec/frontend/issuable_create/components/issuable_form_spec.js b/spec/frontend/issuable_create/components/issuable_form_spec.js index a074fddf091..30b116bc35c 100644 --- a/spec/frontend/issuable_create/components/issuable_form_spec.js +++ b/spec/frontend/issuable_create/components/issuable_form_spec.js @@ -23,6 +23,9 @@ const createComponent = ({ <button class="js-issuable-save">Submit issuable</button> `, }, + stubs: { + MarkdownField, + }, }); }; diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 754ddd96c9b..ea6e4f4a5ed 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -51,6 +51,8 @@ describe('Time series component', () => { }, stubs: { GlPopover: true, + GlLineChart, + GlAreaChart, }, attachTo: document.body, }); @@ -202,7 +204,7 @@ describe('Time series component', () => { describe('when series is of line type', () => { beforeEach(() => { - createWrapper(); + createWrapper({}, mount); wrapper.vm.formatTooltipText(mockLineSeriesData()); return wrapper.vm.$nextTick(); }); diff --git a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js index 6e98ca28071..dbb9fd5f603 100644 --- a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js +++ b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js @@ -1,4 +1,4 @@ -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDropdownItem, GlModal } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue'; import { redirectTo } from '~/lib/utils/url_utility'; @@ -43,6 +43,9 @@ describe('Actions menu', () => { wrapper = shallowMount(ActionsMenu, { propsData: { ...dashboardActionsMenuProps, ...props }, store, + stubs: { + GlModal, + }, ...options, }); }; @@ -82,7 +85,7 @@ describe('Actions menu', () => { it('modal for custom metrics form is rendered', () => { expect(findAddMetricModal().exists()).toBe(true); - expect(findAddMetricModal().attributes().modalid).toBe('addMetric'); + expect(findAddMetricModal().props('modalId')).toBe('addMetric'); }); it('add metric modal submit button exists', () => { diff --git a/spec/frontend/nav/components/responsive_app_spec.js b/spec/frontend/nav/components/responsive_app_spec.js index 7221ea2c5cd..e1b443745e3 100644 --- a/spec/frontend/nav/components/responsive_app_spec.js +++ b/spec/frontend/nav/components/responsive_app_spec.js @@ -111,6 +111,7 @@ describe('~/nav/components/responsive_app.vue', () => { containerClass: 'gl-px-3', frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.namespace, frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.vuexModule, + currentItem: {}, linksPrimary: TEST_NAV_DATA.views.projects.linksPrimary, linksSecondary: TEST_NAV_DATA.views.projects.linksSecondary, }; @@ -118,6 +119,7 @@ describe('~/nav/components/responsive_app.vue', () => { containerClass: 'gl-px-3', frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_GROUPS.namespace, frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_GROUPS.vuexModule, + currentItem: {}, linksPrimary: TEST_NAV_DATA.views.groups.linksPrimary, linksSecondary: TEST_NAV_DATA.views.groups.linksSecondary, }; diff --git a/spec/frontend/nav/components/top_nav_container_view_spec.js b/spec/frontend/nav/components/top_nav_container_view_spec.js index 06d2179b859..0218f09af0a 100644 --- a/spec/frontend/nav/components/top_nav_container_view_spec.js +++ b/spec/frontend/nav/components/top_nav_container_view_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { merge } from 'lodash'; import { nextTick } from 'vue'; import FrequentItemsApp from '~/frequent_items/components/app.vue'; import { FREQUENT_ITEMS_PROJECTS } from '~/frequent_items/constants'; @@ -82,7 +83,9 @@ describe('~/nav/components/top_nav_container_view.vue', () => { it('renders frequent items app', () => { expect(findFrequentItemsApp()).toEqual({ vuexModule: DEFAULT_PROPS.frequentItemsVuexModule, - props: expect.objectContaining(TEST_OTHER_PROPS), + props: expect.objectContaining( + merge({ currentItem: { lastAccessedOn: Date.now() } }, TEST_OTHER_PROPS), + ), attributes: expect.objectContaining(EXTRA_ATTRS), }); }); diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index cd24b9afbdf..59ac75f00e6 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -1,5 +1,5 @@ import { getByRole } from '@testing-library/dom'; -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, mount } from '@vue/test-utils'; import '~/behaviors/markdown/render_gfm'; import DiscussionNotes from '~/notes/components/discussion_notes.vue'; import NoteableNote from '~/notes/components/noteable_note.vue'; @@ -23,8 +23,8 @@ describe('DiscussionNotes', () => { let wrapper; const getList = () => getByRole(wrapper.element, 'list'); - const createComponent = (props) => { - wrapper = shallowMount(DiscussionNotes, { + const createComponent = (props, mountingMethod = shallowMount) => { + wrapper = mountingMethod(DiscussionNotes, { store, propsData: { discussion: discussionMock, @@ -33,7 +33,11 @@ describe('DiscussionNotes', () => { ...props, }, scopedSlots: { - footer: '<p slot-scope="{ showReplies }">showReplies:{{showReplies}}</p>', + footer: ` + <template #default="{ showReplies }"> + <p>showReplies:{{ showReplies }}</p>, + </template> + `, }, slots: { 'avatar-badge': '<span class="avatar-badge-slot-content" />', @@ -112,7 +116,7 @@ describe('DiscussionNotes', () => { }); it('passes down avatar-badge slot content', () => { - createComponent(); + createComponent({}, mount); expect(wrapper.find('.avatar-badge-slot-content').exists()).toBe(true); }); }); diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js index c80ccfa8256..f9be0796546 100644 --- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js @@ -155,7 +155,7 @@ describe('ForkForm component', () => { describe('forks namespaces', () => { beforeEach(() => { mockGetRequest({ namespaces: MOCK_NAMESPACES_RESPONSE }); - createComponent(); + createFullComponent(); }); it('make GET request from endpoint', async () => { diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap index c37f6415898..fc51825f15b 100644 --- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap +++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap @@ -21,11 +21,7 @@ exports[`CiCdAnalyticsAreaChart matches the snapshot 1`] = ` option="[object Object]" thresholds="" width="0" - > - <template /> - - <template /> - </glareachart-stub> + /> </div> </div> `; diff --git a/spec/frontend/terraform/components/terraform_list_spec.js b/spec/frontend/terraform/components/terraform_list_spec.js index 882b7b55b3e..c622f86072d 100644 --- a/spec/frontend/terraform/components/terraform_list_spec.js +++ b/spec/frontend/terraform/components/terraform_list_spec.js @@ -47,6 +47,9 @@ describe('TerraformList', () => { localVue, apolloProvider, propsData, + stubs: { + GlTab, + }, }); }; diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js index 5d09af50420..8214cedc4a1 100644 --- a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js @@ -63,7 +63,7 @@ describe('Commits edit component', () => { beforeEach(() => { createComponent({ header: `<div class="test-header">${testCommitMessage}</div>`, - checkbox: `<label slot="checkbox" class="test-checkbox">${testLabel}</label >`, + checkbox: `<label class="test-checkbox">${testLabel}</label >`, }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 2d00cd8e8d4..cd77d442cbf 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -70,6 +70,9 @@ const createComponent = (customConfig = {}, mergeRequestWidgetGraphql = false) = mergeRequestWidgetGraphql, }, }, + stubs: { + CommitEdit, + }, }); }; diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js index eacc41ccdad..01fc6210d3f 100644 --- a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js +++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js @@ -121,7 +121,9 @@ describe('ImageDiffViewer', () => { :new-size="newSize" :old-size="oldSize" > - <span slot="image-overlay" class="overlay">test</span> + <template #image-overlay> + <span class="overlay">test</span> + </template> </image-diff-viewer> `), }).$mount(); diff --git a/spec/frontend/vue_shared/components/paginated_list_spec.js b/spec/frontend/vue_shared/components/paginated_list_spec.js index c0ee49f194f..9f819cc4e94 100644 --- a/spec/frontend/vue_shared/components/paginated_list_spec.js +++ b/spec/frontend/vue_shared/components/paginated_list_spec.js @@ -7,9 +7,11 @@ describe('Pagination links component', () => { let glPaginatedList; const template = ` - <div class="slot" slot-scope="{ listItem }"> - <span class="item">Item Name: {{listItem.id}}</span> - </div> + <template #default="{ listItem }"> + <div class="slot"> + <span class="item">Item Name: {{ listItem.id }}</span> + </div> + </template> `; const props = { diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap index add0c36a120..cdfe311acd9 100644 --- a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap +++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap @@ -2,20 +2,22 @@ exports[`Resizable Chart Container renders the component 1`] = ` <div> - <div - class="slot" - > - <span - class="width" + <template> + <div + class="slot" > - 0 - </span> - - <span - class="height" - > - 0 - </span> - </div> + <span + class="width" + > + 0 + </span> + + <span + class="height" + > + 0 + </span> + </div> + </template> </div> `; diff --git a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js index 1fce3c5d0b0..40f0c0f29f2 100644 --- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js +++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js @@ -16,10 +16,12 @@ describe('Resizable Chart Container', () => { wrapper = mount(ResizableChartContainer, { scopedSlots: { default: ` - <div class="slot" slot-scope="{ width, height }"> - <span class="width">{{width}}</span> - <span class="height">{{height}}</span> - </div> + <template #default="{ width, height }"> + <div class="slot"> + <span class="width">{{width}}</span> + <span class="height">{{height}}</span> + </div> + </template> `, }, }); diff --git a/spec/models/service_desk_setting_spec.rb b/spec/models/service_desk_setting_spec.rb index 8ccbd983ba1..f99ac84175c 100644 --- a/spec/models/service_desk_setting_spec.rb +++ b/spec/models/service_desk_setting_spec.rb @@ -10,7 +10,7 @@ RSpec.describe ServiceDeskSetting do it { is_expected.to validate_length_of(:outgoing_name).is_at_most(255) } it { is_expected.to validate_length_of(:project_key).is_at_most(255) } it { is_expected.to allow_value('abc123_').for(:project_key) } - it { is_expected.not_to allow_value('abc 12').for(:project_key) } + it { is_expected.not_to allow_value('abc 12').for(:project_key).with_message("can contain only lowercase letters, digits, and '_'.") } it { is_expected.not_to allow_value('Big val').for(:project_key) } describe '.valid_issue_template' do diff --git a/spec/requests/api/graphql/mutations/packages/destroy_package_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_package_spec.rb new file mode 100644 index 00000000000..e5ced419ecf --- /dev/null +++ b/spec/requests/api/graphql/mutations/packages/destroy_package_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Destroying a package' do + using RSpec::Parameterized::TableSyntax + + include GraphqlHelpers + + let_it_be_with_reload(:package) { create(:package) } + let_it_be(:user) { create(:user) } + + let(:project) { package.project } + let(:id) { package.to_global_id.to_s } + + let(:query) do + <<~GQL + errors + GQL + end + + let(:params) { { id: id } } + let(:mutation) { graphql_mutation(:destroy_package, params, query) } + let(:mutation_response) { graphql_mutation_response(:destroyPackage) } + + shared_examples 'destroying the package' do + it 'destroy the package' do + expect(::Packages::DestroyPackageService) + .to receive(:new).with(container: package, current_user: user).and_call_original + + expect { mutation_request }.to change { ::Packages::Package.count }.by(-1) + end + + it_behaves_like 'returning response status', :success + end + + shared_examples 'denying the mutation request' do + it 'does not destroy the package' do + expect(::Packages::DestroyPackageService) + .not_to receive(:new).with(container: package, current_user: user) + + expect { mutation_request }.not_to change { ::Packages::Package.count } + + expect(mutation_response).to be_nil + end + + it_behaves_like 'returning response status', :success + end + + describe 'post graphql mutation' do + subject(:mutation_request) { post_graphql_mutation(mutation, current_user: user) } + + context 'with valid id' do + where(:user_role, :shared_examples_name) do + :maintainer | 'destroying the package' + :developer | 'denying the mutation request' + :reporter | 'denying the mutation request' + :guest | 'denying the mutation request' + :anonymous | 'denying the mutation request' + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'with invalid id' do + let(:params) { { id: 'gid://gitlab/Packages::Package/5555' } } + + it_behaves_like 'denying the mutation request' + end + + context 'when an error occures' do + before do + project.add_maintainer(user) + end + + it 'returns the errors in the response' do + allow_next_found_instance_of(::Packages::Package) do |package| + allow(package).to receive(:destroy!).and_raise(StandardError) + end + + mutation_request + + expect(mutation_response['errors']).to eq(['Failed to remove the package']) + end + end + end +end diff --git a/spec/services/packages/destroy_package_service_spec.rb b/spec/services/packages/destroy_package_service_spec.rb new file mode 100644 index 00000000000..92db8da968c --- /dev/null +++ b/spec/services/packages/destroy_package_service_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::DestroyPackageService do + let_it_be(:user) { create(:user) } + + let!(:package) { create(:npm_package) } + + describe '#execute' do + subject(:service) { described_class.new(container: package, current_user: user) } + + context 'when the user is authorized' do + before do + package.project.add_maintainer(user) + end + + context 'when the destroy is successfull' do + it 'destroy the package' do + expect(package).to receive(:sync_maven_metadata).and_call_original + expect { service.execute }.to change { Packages::Package.count }.by(-1) + end + + it 'returns a success ServiceResponse' do + response = service.execute + + expect(response).to be_a(ServiceResponse) + expect(response).to be_success + expect(response.message).to eq("Package was successfully deleted") + end + end + + context 'when the destroy is not successful' do + before do + allow(package).to receive(:destroy!).and_raise(StandardError, "test") + end + + it 'returns an error ServiceResponse' do + response = service.execute + + expect(package).not_to receive(:sync_maven_metadata) + expect(response).to be_a(ServiceResponse) + expect(response).to be_error + expect(response.message).to eq("Failed to remove the package") + expect(response.status).to eq(:error) + end + end + end + + context 'when the user is not authorized' do + it 'returns an error ServiceResponse' do + response = service.execute + + expect(response).to be_a(ServiceResponse) + expect(response).to be_error + expect(response.message).to eq("You don't have access to this package") + expect(response.status).to eq(:error) + end + end + end +end diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue b/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue index e9f3acea9d8..2d354d605f8 100644 --- a/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue +++ b/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue @@ -10,7 +10,7 @@ @visible="onScrollerVisible" v-on="listeners" > - <template slot-scope="{ item: itemWithSize, index, active }"> + <template #default="{ item: itemWithSize, index, active }"> <slot v-bind="{ item: itemWithSize.item, @@ -20,10 +20,10 @@ }" /> </template> - <template slot="before"> + <template #before> <slot name="before" /> </template> - <template slot="after"> + <template #after> <slot name="after" /> </template> </RecycleScroller> |