Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-03 00:10:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-03 00:10:43 +0300
commita961982252b5e33af6d7de358fbae28cb8cfd104 (patch)
treec8cb5fa18814881e76fa2c757855ba832f9e49f3
parent4e06ca9e7d67aee59d691a363dabba46d53d34ba (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue2
-rw-r--r--app/assets/javascripts/design_management/index.js2
-rw-r--r--app/assets/javascripts/design_management_legacy/components/app.vue3
-rw-r--r--app/assets/javascripts/design_management_legacy/components/delete_button.vue64
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_destroyer.vue66
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_note_pin.vue61
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_notes/design_discussion.vue297
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_notes/design_note.vue157
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_notes/design_reply_form.vue141
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_notes/toggle_replies_widget.vue70
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_overlay.vue287
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_presentation.vue322
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_scaler.vue65
-rw-r--r--app/assets/javascripts/design_management_legacy/components/design_sidebar.vue178
-rw-r--r--app/assets/javascripts/design_management_legacy/components/image.vue110
-rw-r--r--app/assets/javascripts/design_management_legacy/components/list/item.vue172
-rw-r--r--app/assets/javascripts/design_management_legacy/components/toolbar/index.vue125
-rw-r--r--app/assets/javascripts/design_management_legacy/components/toolbar/pagination.vue83
-rw-r--r--app/assets/javascripts/design_management_legacy/components/toolbar/pagination_button.vue48
-rw-r--r--app/assets/javascripts/design_management_legacy/components/upload/button.vue58
-rw-r--r--app/assets/javascripts/design_management_legacy/components/upload/design_dropzone.vue134
-rw-r--r--app/assets/javascripts/design_management_legacy/components/upload/design_version_dropdown.vue76
-rw-r--r--app/assets/javascripts/design_management_legacy/constants.js16
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql.js45
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/design.fragment.graphql24
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/design_list.fragment.graphql8
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/design_note.fragment.graphql29
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/diff_refs.fragment.graphql5
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/discussion_resolved_status.fragment.graphql9
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/note_permissions.fragment.graphql3
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/fragments/version.fragment.graphql4
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/create_image_diff_note.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/create_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/destroy_design.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/toggle_resolve_discussion.mutation.graphql17
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql3
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/update_image_diff_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/update_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/mutations/upload_design.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/queries/active_discussion.query.graphql6
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/queries/app_data.query.graphql4
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/queries/design_permissions.query.graphql10
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/queries/get_design.query.graphql31
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/queries/get_design_list.query.graphql26
-rw-r--r--app/assets/javascripts/design_management_legacy/graphql/typedefs.graphql12
-rw-r--r--app/assets/javascripts/design_management_legacy/index.js61
-rw-r--r--app/assets/javascripts/design_management_legacy/mixins/all_designs.js49
-rw-r--r--app/assets/javascripts/design_management_legacy/mixins/all_versions.js62
-rw-r--r--app/assets/javascripts/design_management_legacy/pages/design/index.vue378
-rw-r--r--app/assets/javascripts/design_management_legacy/pages/index.vue323
-rw-r--r--app/assets/javascripts/design_management_legacy/router/constants.js3
-rw-r--r--app/assets/javascripts/design_management_legacy/router/index.js35
-rw-r--r--app/assets/javascripts/design_management_legacy/router/routes.js44
-rw-r--r--app/assets/javascripts/design_management_legacy/utils/cache_update.js276
-rw-r--r--app/assets/javascripts/design_management_legacy/utils/design_management_utils.js128
-rw-r--r--app/assets/javascripts/design_management_legacy/utils/error_messages.js95
-rw-r--r--app/assets/javascripts/design_management_legacy/utils/tracking.js26
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue2
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue51
-rw-r--r--app/assets/javascripts/incidents/constants.js1
-rw-r--r--app/assets/javascripts/issuables_list/components/issuables_list_app.vue2
-rw-r--r--app/assets/javascripts/mr_popover/components/mr_popover.vue2
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue2
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js6
-rw-r--r--app/assets/javascripts/releases/components/app_index.vue7
-rw-r--r--app/assets/javascripts/releases/components/app_show.vue2
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue2
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue2
-rw-r--r--app/views/projects/issues/_design_management.html.haml19
-rw-r--r--app/views/projects/issues/_tabs.html.haml14
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--changelogs/unreleased/229636-incident-empty-state-polish.yml5
-rw-r--r--changelogs/unreleased/agent_gitops_sync_count.yml5
-rw-r--r--changelogs/unreleased/dblessing-doorkeeper-hex-generator.yml5
-rw-r--r--config/initializers/doorkeeper.rb4
-rw-r--r--doc/administration/auth/ldap/ldap-troubleshooting.md21
-rw-r--r--doc/api/snippets.md6
-rw-r--r--doc/user/group/saml_sso/index.md2
-rw-r--r--doc/user/project/issues/design_management.md29
-rw-r--r--lib/api/internal/kubernetes.rb19
-rw-r--r--lib/gitlab/error_tracking.rb2
-rw-r--r--lib/gitlab/error_tracking/processor/grpc_error_processor.rb71
-rw-r--r--lib/gitlab/git.rb1
-rw-r--r--lib/gitlab/git/base_error.rb21
-rw-r--r--lib/gitlab/usage_data.rb3
-rw-r--r--lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb22
-rw-r--r--lib/gitlab/usage_data_counters/redis_counter.rb6
-rw-r--r--locale/gitlab.pot45
-rw-r--r--spec/features/projects/issues/design_management/user_paginates_designs_spec.rb59
-rw-r--r--spec/features/projects/issues/design_management/user_permissions_upload_spec.rb29
-rw-r--r--spec/features/projects/issues/design_management/user_uploads_designs_spec.rb79
-rw-r--r--spec/features/projects/issues/design_management/user_views_design_spec.rb39
-rw-r--r--spec/features/projects/issues/design_management/user_views_designs_spec.rb77
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js7
-rw-r--r--spec/frontend/design_management_legacy/components/__snapshots__/design_note_pin_spec.js.snap42
-rw-r--r--spec/frontend/design_management_legacy/components/__snapshots__/design_presentation_spec.js.snap104
-rw-r--r--spec/frontend/design_management_legacy/components/__snapshots__/design_scaler_spec.js.snap115
-rw-r--r--spec/frontend/design_management_legacy/components/__snapshots__/image_spec.js.snap68
-rw-r--r--spec/frontend/design_management_legacy/components/delete_button_spec.js51
-rw-r--r--spec/frontend/design_management_legacy/components/design_note_pin_spec.js49
-rw-r--r--spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_note_spec.js.snap67
-rw-r--r--spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_reply_form_spec.js.snap15
-rw-r--r--spec/frontend/design_management_legacy/components/design_notes/design_discussion_spec.js318
-rw-r--r--spec/frontend/design_management_legacy/components/design_notes/design_note_spec.js170
-rw-r--r--spec/frontend/design_management_legacy/components/design_notes/design_reply_form_spec.js184
-rw-r--r--spec/frontend/design_management_legacy/components/design_notes/toggle_replies_widget_spec.js98
-rw-r--r--spec/frontend/design_management_legacy/components/design_overlay_spec.js410
-rw-r--r--spec/frontend/design_management_legacy/components/design_presentation_spec.js553
-rw-r--r--spec/frontend/design_management_legacy/components/design_scaler_spec.js67
-rw-r--r--spec/frontend/design_management_legacy/components/design_sidebar_spec.js236
-rw-r--r--spec/frontend/design_management_legacy/components/image_spec.js133
-rw-r--r--spec/frontend/design_management_legacy/components/list/__snapshots__/item_spec.js.snap149
-rw-r--r--spec/frontend/design_management_legacy/components/list/item_spec.js168
-rw-r--r--spec/frontend/design_management_legacy/components/toolbar/__snapshots__/index_spec.js.snap61
-rw-r--r--spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_button_spec.js.snap28
-rw-r--r--spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_spec.js.snap29
-rw-r--r--spec/frontend/design_management_legacy/components/toolbar/index_spec.js123
-rw-r--r--spec/frontend/design_management_legacy/components/toolbar/pagination_button_spec.js61
-rw-r--r--spec/frontend/design_management_legacy/components/toolbar/pagination_spec.js79
-rw-r--r--spec/frontend/design_management_legacy/components/upload/__snapshots__/button_spec.js.snap79
-rw-r--r--spec/frontend/design_management_legacy/components/upload/__snapshots__/design_dropzone_spec.js.snap455
-rw-r--r--spec/frontend/design_management_legacy/components/upload/__snapshots__/design_version_dropdown_spec.js.snap111
-rw-r--r--spec/frontend/design_management_legacy/components/upload/button_spec.js59
-rw-r--r--spec/frontend/design_management_legacy/components/upload/design_dropzone_spec.js132
-rw-r--r--spec/frontend/design_management_legacy/components/upload/design_version_dropdown_spec.js122
-rw-r--r--spec/frontend/design_management_legacy/components/upload/mock_data/all_versions.js14
-rw-r--r--spec/frontend/design_management_legacy/mock_data/all_versions.js8
-rw-r--r--spec/frontend/design_management_legacy/mock_data/design.js74
-rw-r--r--spec/frontend/design_management_legacy/mock_data/designs.js17
-rw-r--r--spec/frontend/design_management_legacy/mock_data/no_designs.js11
-rw-r--r--spec/frontend/design_management_legacy/mock_data/notes.js46
-rw-r--r--spec/frontend/design_management_legacy/pages/__snapshots__/index_spec.js.snap263
-rw-r--r--spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap216
-rw-r--r--spec/frontend/design_management_legacy/pages/design/index_spec.js291
-rw-r--r--spec/frontend/design_management_legacy/pages/index_spec.js536
-rw-r--r--spec/frontend/design_management_legacy/router_spec.js82
-rw-r--r--spec/frontend/design_management_legacy/utils/cache_update_spec.js44
-rw-r--r--spec/frontend/design_management_legacy/utils/design_management_utils_spec.js176
-rw-r--r--spec/frontend/design_management_legacy/utils/error_messages_spec.js62
-rw-r--r--spec/frontend/design_management_legacy/utils/tracking_spec.js59
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js43
-rw-r--r--spec/frontend/issuables_list/components/issuables_list_app_spec.js6
-rw-r--r--spec/frontend/releases/components/app_show_spec.js2
-rw-r--r--spec/frontend/repository/components/table/index_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js2
-rw-r--r--spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb75
-rw-r--r--spec/lib/gitlab/git/base_error_spec.rb23
-rw-r--r--spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb23
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb48
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb6
-rw-r--r--spec/requests/api/internal/kubernetes_spec.rb106
159 files changed, 451 insertions, 11153 deletions
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index 09d7c0329a9..7b53020fc49 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -5,7 +5,7 @@ import {
GlLink,
GlLoadingIcon,
GlPagination,
- GlSkeletonLoading,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlSprintf,
GlTable,
} from '@gitlab/ui';
diff --git a/app/assets/javascripts/design_management/index.js b/app/assets/javascripts/design_management/index.js
index 20c9cacf83f..1a87dd38137 100644
--- a/app/assets/javascripts/design_management/index.js
+++ b/app/assets/javascripts/design_management/index.js
@@ -4,7 +4,7 @@ import App from './components/app.vue';
import apolloProvider from './graphql';
export default () => {
- const el = document.querySelector('.js-design-management-new');
+ const el = document.querySelector('.js-design-management');
const { issueIid, projectPath, issuePath } = el.dataset;
const router = createRouter(issuePath);
diff --git a/app/assets/javascripts/design_management_legacy/components/app.vue b/app/assets/javascripts/design_management_legacy/components/app.vue
deleted file mode 100644
index 98240aef810..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/app.vue
+++ /dev/null
@@ -1,3 +0,0 @@
-<template>
- <router-view />
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/delete_button.vue b/app/assets/javascripts/design_management_legacy/components/delete_button.vue
deleted file mode 100644
index 1fd902c9ed7..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/delete_button.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<script>
-import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
-import { uniqueId } from 'lodash';
-
-export default {
- name: 'DeleteButton',
- components: {
- GlDeprecatedButton,
- GlModal,
- },
- directives: {
- GlModalDirective,
- },
- props: {
- isDeleting: {
- type: Boolean,
- required: false,
- default: false,
- },
- buttonClass: {
- type: String,
- required: false,
- default: '',
- },
- buttonVariant: {
- type: String,
- required: false,
- default: '',
- },
- hasSelectedDesigns: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- data() {
- return {
- modalId: uniqueId('design-deletion-confirmation-'),
- };
- },
-};
-</script>
-
-<template>
- <div>
- <gl-modal
- :modal-id="modalId"
- :title="s__('DesignManagement|Delete designs confirmation')"
- :ok-title="s__('DesignManagement|Delete')"
- ok-variant="danger"
- @ok="$emit('deleteSelectedDesigns')"
- >
- <p>{{ s__('DesignManagement|Are you sure you want to delete the selected designs?') }}</p>
- </gl-modal>
- <gl-deprecated-button
- v-gl-modal-directive="modalId"
- :variant="buttonVariant"
- :disabled="isDeleting || !hasSelectedDesigns"
- :class="buttonClass"
- >
- <slot></slot>
- </gl-deprecated-button>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_destroyer.vue b/app/assets/javascripts/design_management_legacy/components/design_destroyer.vue
deleted file mode 100644
index 62460ca551c..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_destroyer.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<script>
-import { ApolloMutation } from 'vue-apollo';
-import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import destroyDesignMutation from '../graphql/mutations/destroy_design.mutation.graphql';
-import { updateStoreAfterDesignsDelete } from '../utils/cache_update';
-
-export default {
- components: {
- ApolloMutation,
- },
- props: {
- filenames: {
- type: Array,
- required: true,
- },
- projectPath: {
- type: String,
- required: true,
- },
- iid: {
- type: String,
- required: true,
- },
- },
- computed: {
- projectQueryBody() {
- return {
- query: getDesignListQuery,
- variables: { fullPath: this.projectPath, iid: this.iid, atVersion: null },
- };
- },
- },
- methods: {
- updateStoreAfterDelete(
- store,
- {
- data: { designManagementDelete },
- },
- ) {
- updateStoreAfterDesignsDelete(
- store,
- designManagementDelete,
- this.projectQueryBody,
- this.filenames,
- );
- },
- },
- destroyDesignMutation,
-};
-</script>
-
-<template>
- <apollo-mutation
- #default="{ mutate, loading, error }"
- :mutation="$options.destroyDesignMutation"
- :variables="{
- filenames,
- projectPath,
- iid,
- }"
- :update="updateStoreAfterDelete"
- v-on="$listeners"
- >
- <slot v-bind="{ mutate, loading, error }"></slot>
- </apollo-mutation>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_note_pin.vue b/app/assets/javascripts/design_management_legacy/components/design_note_pin.vue
deleted file mode 100644
index 2b5e62c2870..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_note_pin.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
-
-export default {
- name: 'DesignNotePin',
- components: {
- GlIcon,
- },
- props: {
- position: {
- type: Object,
- required: true,
- },
- label: {
- type: Number,
- required: false,
- default: null,
- },
- repositioning: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- computed: {
- isNewNote() {
- return this.label === null;
- },
- pinStyle() {
- return this.repositioning ? { ...this.position, cursor: 'move' } : this.position;
- },
- pinLabel() {
- return this.isNewNote
- ? __('Comment form position')
- : sprintf(__("Comment '%{label}' position"), { label: this.label });
- },
- },
-};
-</script>
-
-<template>
- <button
- :style="pinStyle"
- :aria-label="pinLabel"
- :class="{
- 'btn-transparent comment-indicator': isNewNote,
- 'js-image-badge badge badge-pill': !isNewNote,
- }"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0"
- type="button"
- @mousedown="$emit('mousedown', $event)"
- @mouseup="$emit('mouseup', $event)"
- @click="$emit('click', $event)"
- >
- <gl-icon v-if="isNewNote" name="image-comment-dark" :size="24" />
- <template v-else>
- {{ label }}
- </template>
- </button>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management_legacy/components/design_notes/design_discussion.vue
deleted file mode 100644
index 6a20517eed7..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_notes/design_discussion.vue
+++ /dev/null
@@ -1,297 +0,0 @@
-<script>
-import { ApolloMutation } from 'vue-apollo';
-import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import allVersionsMixin from '../../mixins/all_versions';
-import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql';
-import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql';
-import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
-import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql';
-import DesignNote from './design_note.vue';
-import DesignReplyForm from './design_reply_form.vue';
-import { updateStoreAfterAddDiscussionComment } from '../../utils/cache_update';
-import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
-import ToggleRepliesWidget from './toggle_replies_widget.vue';
-
-export default {
- components: {
- ApolloMutation,
- DesignNote,
- ReplyPlaceholder,
- DesignReplyForm,
- GlIcon,
- GlLoadingIcon,
- GlLink,
- ToggleRepliesWidget,
- TimeAgoTooltip,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- mixins: [allVersionsMixin],
- props: {
- discussion: {
- type: Object,
- required: true,
- },
- noteableId: {
- type: String,
- required: true,
- },
- designId: {
- type: String,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: false,
- default: '',
- },
- resolvedDiscussionsExpanded: {
- type: Boolean,
- required: true,
- },
- discussionWithOpenForm: {
- type: String,
- required: true,
- },
- },
- apollo: {
- activeDiscussion: {
- query: activeDiscussionQuery,
- result({ data }) {
- const discussionId = data.activeDiscussion.id;
- if (this.discussion.resolved && !this.resolvedDiscussionsExpanded) {
- return;
- }
- // We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists
- // We don't want scrollIntoView to be triggered from the discussion click itself
- if (
- discussionId &&
- data.activeDiscussion.source === ACTIVE_DISCUSSION_SOURCE_TYPES.pin &&
- discussionId === this.discussion.notes[0].id
- ) {
- this.$el.scrollIntoView({
- behavior: 'smooth',
- inline: 'start',
- });
- }
- },
- },
- },
- data() {
- return {
- discussionComment: '',
- isFormRendered: false,
- activeDiscussion: {},
- isResolving: false,
- shouldChangeResolvedStatus: false,
- areRepliesCollapsed: this.discussion.resolved,
- };
- },
- computed: {
- mutationPayload() {
- return {
- noteableId: this.noteableId,
- body: this.discussionComment,
- discussionId: this.discussion.id,
- };
- },
- designVariables() {
- return {
- fullPath: this.projectPath,
- iid: this.issueIid,
- filenames: [this.$route.params.id],
- atVersion: this.designsVersion,
- };
- },
- isDiscussionHighlighted() {
- return this.discussion.notes[0].id === this.activeDiscussion.id;
- },
- resolveCheckboxText() {
- return this.discussion.resolved
- ? s__('DesignManagement|Unresolve thread')
- : s__('DesignManagement|Resolve thread');
- },
- firstNote() {
- return this.discussion.notes[0];
- },
- discussionReplies() {
- return this.discussion.notes.slice(1);
- },
- areRepliesShown() {
- return !this.discussion.resolved || !this.areRepliesCollapsed;
- },
- resolveIconName() {
- return this.discussion.resolved ? 'check-circle-filled' : 'check-circle';
- },
- isRepliesWidgetVisible() {
- return this.discussion.resolved && this.discussionReplies.length > 0;
- },
- isReplyPlaceholderVisible() {
- return this.areRepliesShown || !this.discussionReplies.length;
- },
- isFormVisible() {
- return this.isFormRendered && this.discussionWithOpenForm === this.discussion.id;
- },
- },
- methods: {
- addDiscussionComment(
- store,
- {
- data: { createNote },
- },
- ) {
- updateStoreAfterAddDiscussionComment(
- store,
- createNote,
- getDesignQuery,
- this.designVariables,
- this.discussion.id,
- );
- },
- onDone() {
- this.discussionComment = '';
- this.hideForm();
- if (this.shouldChangeResolvedStatus) {
- this.toggleResolvedStatus();
- }
- },
- onCreateNoteError(err) {
- this.$emit('createNoteError', err);
- },
- hideForm() {
- this.isFormRendered = false;
- this.discussionComment = '';
- },
- showForm() {
- this.$emit('openForm', this.discussion.id);
- this.isFormRendered = true;
- },
- toggleResolvedStatus() {
- this.isResolving = true;
- this.$apollo
- .mutate({
- mutation: toggleResolveDiscussionMutation,
- variables: { id: this.discussion.id, resolve: !this.discussion.resolved },
- })
- .then(({ data }) => {
- if (data.errors?.length > 0) {
- this.$emit('resolveDiscussionError', data.errors[0]);
- }
- })
- .catch(err => {
- this.$emit('resolveDiscussionError', err);
- })
- .finally(() => {
- this.isResolving = false;
- });
- },
- },
- createNoteMutation,
-};
-</script>
-
-<template>
- <div class="design-discussion-wrapper">
- <div
- class="badge badge-pill gl-display-flex gl-align-items-center gl-justify-content-center"
- :class="{ resolved: discussion.resolved }"
- type="button"
- >
- {{ discussion.index }}
- </div>
- <ul
- class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none"
- data-qa-selector="design_discussion_content"
- >
- <design-note
- :note="firstNote"
- :markdown-preview-path="markdownPreviewPath"
- :is-resolving="isResolving"
- :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }"
- @error="$emit('updateNoteError', $event)"
- >
- <template v-if="discussion.resolvable" #resolveDiscussion>
- <button
- v-gl-tooltip
- :class="{ 'is-active': discussion.resolved }"
- :title="resolveCheckboxText"
- :aria-label="resolveCheckboxText"
- type="button"
- class="line-resolve-btn note-action-button gl-mr-3"
- data-testid="resolve-button"
- @click.stop="toggleResolvedStatus"
- >
- <gl-icon v-if="!isResolving" :name="resolveIconName" data-testid="resolve-icon" />
- <gl-loading-icon v-else inline />
- </button>
- </template>
- <template v-if="discussion.resolved" #resolvedStatus>
- <p class="gl-text-gray-500 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message">
- {{ __('Resolved by') }}
- <gl-link
- class="gl-text-gray-500 gl-text-decoration-none gl-font-sm link-inherit-color"
- :href="discussion.resolvedBy.webUrl"
- target="_blank"
- >{{ discussion.resolvedBy.name }}</gl-link
- >
- <time-ago-tooltip :time="discussion.resolvedAt" tooltip-placement="bottom" />
- </p>
- </template>
- </design-note>
- <toggle-replies-widget
- v-if="isRepliesWidgetVisible"
- :collapsed="areRepliesCollapsed"
- :replies="discussionReplies"
- @toggle="areRepliesCollapsed = !areRepliesCollapsed"
- />
- <design-note
- v-for="note in discussionReplies"
- v-show="areRepliesShown"
- :key="note.id"
- :note="note"
- :markdown-preview-path="markdownPreviewPath"
- :is-resolving="isResolving"
- :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }"
- @error="$emit('updateNoteError', $event)"
- />
- <li v-show="isReplyPlaceholderVisible" class="reply-wrapper">
- <reply-placeholder
- v-if="!isFormVisible"
- class="qa-discussion-reply"
- :button-text="__('Reply...')"
- @onClick="showForm"
- />
- <apollo-mutation
- v-else
- #default="{ mutate, loading }"
- :mutation="$options.createNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- :update="addDiscussionComment"
- @done="onDone"
- @error="onCreateNoteError"
- >
- <design-reply-form
- v-model="discussionComment"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- @submitForm="mutate"
- @cancelForm="hideForm"
- >
- <template v-if="discussion.resolvable" #resolveCheckbox>
- <label data-testid="resolve-checkbox">
- <input v-model="shouldChangeResolvedStatus" type="checkbox" />
- {{ resolveCheckboxText }}
- </label>
- </template>
- </design-reply-form>
- </apollo-mutation>
- </li>
- </ul>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_notes/design_note.vue b/app/assets/javascripts/design_management_legacy/components/design_notes/design_note.vue
deleted file mode 100644
index 93f8fbc5ba3..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_notes/design_note.vue
+++ /dev/null
@@ -1,157 +0,0 @@
-<script>
-/* eslint-disable vue/no-v-html */
-import { ApolloMutation } from 'vue-apollo';
-import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import updateNoteMutation from '../../graphql/mutations/update_note.mutation.graphql';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import DesignReplyForm from './design_reply_form.vue';
-import { findNoteId } from '../../utils/design_management_utils';
-import { hasErrors } from '../../utils/cache_update';
-
-export default {
- components: {
- UserAvatarLink,
- TimelineEntryItem,
- TimeAgoTooltip,
- DesignReplyForm,
- ApolloMutation,
- GlIcon,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- note: {
- type: Object,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- noteText: this.note.body,
- isEditing: false,
- };
- },
- computed: {
- author() {
- return this.note.author;
- },
- noteAnchorId() {
- return findNoteId(this.note.id);
- },
- isNoteLinked() {
- return this.$route.hash === `#note_${this.noteAnchorId}`;
- },
- mutationPayload() {
- return {
- id: this.note.id,
- body: this.noteText,
- };
- },
- isEditButtonVisible() {
- return !this.isEditing && this.note.userPermissions.adminNote;
- },
- },
- mounted() {
- if (this.isNoteLinked) {
- this.$refs.anchor.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' });
- }
- },
- methods: {
- hideForm() {
- this.isEditing = false;
- this.noteText = this.note.body;
- },
- onDone({ data }) {
- this.hideForm();
- if (hasErrors(data.updateNote)) {
- this.$emit('error', data.errors[0]);
- }
- },
- },
- updateNoteMutation,
-};
-</script>
-
-<template>
- <timeline-entry-item :id="`note_${noteAnchorId}`" ref="anchor" class="design-note note-form">
- <user-avatar-link
- :link-href="author.webUrl"
- :img-src="author.avatarUrl"
- :img-alt="author.username"
- :img-size="40"
- />
- <div class="d-flex justify-content-between">
- <div>
- <a
- v-once
- :href="author.webUrl"
- class="js-user-link"
- :data-user-id="author.id"
- :data-username="author.username"
- >
- <span class="note-header-author-name bold">{{ author.name }}</span>
- <span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
- <span class="note-headline-light">@{{ author.username }}</span>
- </a>
- <span class="note-headline-light note-headline-meta">
- <span class="system-note-message"> <slot></slot> </span>
- <template v-if="note.createdAt">
- <span class="system-note-separator"></span>
- <a class="note-timestamp system-note-separator" :href="`#note_${noteAnchorId}`">
- <time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" />
- </a>
- </template>
- </span>
- </div>
- <div class="gl-display-flex">
- <slot name="resolveDiscussion"></slot>
- <button
- v-if="isEditButtonVisible"
- v-gl-tooltip
- type="button"
- :title="__('Edit comment')"
- class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button"
- @click="isEditing = true"
- >
- <gl-icon name="pencil" class="link-highlight" />
- </button>
- </div>
- </div>
- <template v-if="!isEditing">
- <div
- class="note-text js-note-text md"
- data-qa-selector="note_content"
- v-html="note.bodyHtml"
- ></div>
- <slot name="resolvedStatus"></slot>
- </template>
- <apollo-mutation
- v-else
- #default="{ mutate, loading }"
- :mutation="$options.updateNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- @error="$emit('error', $event)"
- @done="onDone"
- >
- <design-reply-form
- v-model="noteText"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- :is-new-comment="false"
- class="mt-5"
- @submitForm="mutate"
- @cancelForm="hideForm"
- />
- </apollo-mutation>
- </timeline-entry-item>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management_legacy/components/design_notes/design_reply_form.vue
deleted file mode 100644
index 969034909f2..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_notes/design_reply_form.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-<script>
-import { GlDeprecatedButton, GlModal } from '@gitlab/ui';
-import MarkdownField from '~/vue_shared/components/markdown/field.vue';
-import { s__ } from '~/locale';
-
-export default {
- name: 'DesignReplyForm',
- components: {
- MarkdownField,
- GlDeprecatedButton,
- GlModal,
- },
- props: {
- markdownPreviewPath: {
- type: String,
- required: false,
- default: '',
- },
- value: {
- type: String,
- required: true,
- },
- isSaving: {
- type: Boolean,
- required: true,
- },
- isNewComment: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- data() {
- return {
- formText: this.value,
- };
- },
- computed: {
- hasValue() {
- return this.value.trim().length > 0;
- },
- modalSettings() {
- if (this.isNewComment) {
- return {
- title: s__('DesignManagement|Cancel comment confirmation'),
- okTitle: s__('DesignManagement|Discard comment'),
- cancelTitle: s__('DesignManagement|Keep comment'),
- content: s__('DesignManagement|Are you sure you want to cancel creating this comment?'),
- };
- }
- return {
- title: s__('DesignManagement|Cancel comment update confirmation'),
- okTitle: s__('DesignManagement|Cancel changes'),
- cancelTitle: s__('DesignManagement|Keep changes'),
- content: s__('DesignManagement|Are you sure you want to cancel changes to this comment?'),
- };
- },
- buttonText() {
- return this.isNewComment
- ? s__('DesignManagement|Comment')
- : s__('DesignManagement|Save comment');
- },
- },
- mounted() {
- this.focusInput();
- },
- methods: {
- submitForm() {
- if (this.hasValue) this.$emit('submitForm');
- },
- cancelComment() {
- if (this.hasValue && this.formText !== this.value) {
- this.$refs.cancelCommentModal.show();
- } else {
- this.$emit('cancelForm');
- }
- },
- focusInput() {
- this.$refs.textarea.focus();
- },
- },
-};
-</script>
-
-<template>
- <form class="new-note common-note-form" @submit.prevent>
- <markdown-field
- :markdown-preview-path="markdownPreviewPath"
- :can-attach-file="false"
- :enable-autocomplete="true"
- :textarea-value="value"
- markdown-docs-path="/help/user/markdown"
- class="bordered-box"
- >
- <template #textarea>
- <textarea
- ref="textarea"
- :value="value"
- class="note-textarea js-gfm-input js-autosize markdown-area"
- dir="auto"
- data-supports-quick-actions="false"
- data-qa-selector="note_textarea"
- :aria-label="__('Description')"
- :placeholder="__('Write a comment…')"
- @input="$emit('input', $event.target.value)"
- @keydown.meta.enter="submitForm"
- @keydown.ctrl.enter="submitForm"
- @keyup.esc.stop="cancelComment"
- >
- </textarea>
- </template>
- </markdown-field>
- <slot name="resolveCheckbox"></slot>
- <div class="note-form-actions gl-display-flex gl-justify-content-space-between">
- <gl-deprecated-button
- ref="submitButton"
- :disabled="!hasValue || isSaving"
- variant="success"
- type="submit"
- data-track-event="click_button"
- data-qa-selector="save_comment_button"
- @click="$emit('submitForm')"
- >
- {{ buttonText }}
- </gl-deprecated-button>
- <gl-deprecated-button ref="cancelButton" @click="cancelComment">{{
- __('Cancel')
- }}</gl-deprecated-button>
- </div>
- <gl-modal
- ref="cancelCommentModal"
- ok-variant="danger"
- :title="modalSettings.title"
- :ok-title="modalSettings.okTitle"
- :cancel-title="modalSettings.cancelTitle"
- modal-id="cancel-comment-modal"
- @ok="$emit('cancelForm')"
- >{{ modalSettings.content }}
- </gl-modal>
- </form>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_notes/toggle_replies_widget.vue b/app/assets/javascripts/design_management_legacy/components/design_notes/toggle_replies_widget.vue
deleted file mode 100644
index 2e366282de3..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_notes/toggle_replies_widget.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-<script>
-import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
-import { __, n__ } from '~/locale';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-
-export default {
- name: 'ToggleNotesWidget',
- components: {
- GlIcon,
- GlButton,
- GlLink,
- TimeAgoTooltip,
- },
- props: {
- collapsed: {
- type: Boolean,
- required: true,
- },
- replies: {
- type: Array,
- required: true,
- },
- },
- computed: {
- lastReply() {
- return this.replies[this.replies.length - 1];
- },
- iconName() {
- return this.collapsed ? 'chevron-right' : 'chevron-down';
- },
- toggleText() {
- return this.collapsed
- ? `${this.replies.length} ${n__('reply', 'replies', this.replies.length)}`
- : __('Collapse replies');
- },
- },
-};
-</script>
-
-<template>
- <li
- class="toggle-comments gl-bg-gray-50 gl-display-flex gl-align-items-center gl-py-3"
- :class="{ expanded: !collapsed }"
- data-testid="toggle-comments-wrapper"
- >
- <gl-icon :name="iconName" class="gl-ml-3" @click.stop="$emit('toggle')" />
- <gl-button
- variant="link"
- class="toggle-comments-button gl-ml-2 gl-mr-2"
- @click.stop="$emit('toggle')"
- >
- {{ toggleText }}
- </gl-button>
- <template v-if="collapsed">
- <span class="gl-text-gray-500">{{ __('Last reply by') }}</span>
- <gl-link
- :href="lastReply.author.webUrl"
- target="_blank"
- class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-ml-2 gl-mr-2"
- >
- {{ lastReply.author.name }}
- </gl-link>
- <time-ago-tooltip
- :time="lastReply.createdAt"
- tooltip-placement="bottom"
- class="gl-text-gray-500"
- />
- </template>
- </li>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_overlay.vue b/app/assets/javascripts/design_management_legacy/components/design_overlay.vue
deleted file mode 100644
index 926e7c74802..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_overlay.vue
+++ /dev/null
@@ -1,287 +0,0 @@
-<script>
-import activeDiscussionQuery from '../graphql/queries/active_discussion.query.graphql';
-import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
-import DesignNotePin from './design_note_pin.vue';
-import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
-
-export default {
- name: 'DesignOverlay',
- components: {
- DesignNotePin,
- },
- props: {
- dimensions: {
- type: Object,
- required: true,
- },
- position: {
- type: Object,
- required: true,
- },
- notes: {
- type: Array,
- required: false,
- default: () => [],
- },
- currentCommentForm: {
- type: Object,
- required: false,
- default: null,
- },
- disableCommenting: {
- type: Boolean,
- required: false,
- default: false,
- },
- resolvedDiscussionsExpanded: {
- type: Boolean,
- required: true,
- },
- },
- apollo: {
- activeDiscussion: {
- query: activeDiscussionQuery,
- },
- },
- data() {
- return {
- movingNoteNewPosition: null,
- movingNoteStartPosition: null,
- activeDiscussion: {},
- };
- },
- computed: {
- overlayStyle() {
- const cursor = this.disableCommenting ? 'unset' : undefined;
-
- return {
- cursor,
- width: `${this.dimensions.width}px`,
- height: `${this.dimensions.height}px`,
- ...this.position,
- };
- },
- isMovingCurrentComment() {
- return Boolean(this.movingNoteStartPosition && !this.movingNoteStartPosition.noteId);
- },
- currentCommentPositionStyle() {
- return this.isMovingCurrentComment && this.movingNoteNewPosition
- ? this.getNotePositionStyle(this.movingNoteNewPosition)
- : this.getNotePositionStyle(this.currentCommentForm);
- },
- },
- methods: {
- setNewNoteCoordinates({ x, y }) {
- this.$emit('openCommentForm', { x, y });
- },
- getNoteRelativePosition(position) {
- const { x, y, width, height } = position;
- const widthRatio = this.dimensions.width / width;
- const heightRatio = this.dimensions.height / height;
- return {
- left: Math.round(x * widthRatio),
- top: Math.round(y * heightRatio),
- };
- },
- getNotePositionStyle(position) {
- const { left, top } = this.getNoteRelativePosition(position);
- return {
- left: `${left}px`,
- top: `${top}px`,
- };
- },
- getMovingNotePositionDelta(e) {
- let deltaX = 0;
- let deltaY = 0;
-
- if (this.movingNoteStartPosition) {
- const { clientX, clientY } = this.movingNoteStartPosition;
- deltaX = e.clientX - clientX;
- deltaY = e.clientY - clientY;
- }
-
- return {
- deltaX,
- deltaY,
- };
- },
- isMovingNote(noteId) {
- const movingNoteId = this.movingNoteStartPosition?.noteId;
- return Boolean(movingNoteId && movingNoteId === noteId);
- },
- canMoveNote(note) {
- const { userPermissions } = note;
- const { adminNote } = userPermissions || {};
-
- return Boolean(adminNote);
- },
- isPositionInOverlay(position) {
- const { top, left } = this.getNoteRelativePosition(position);
- const { height, width } = this.dimensions;
-
- return top >= 0 && top <= height && left >= 0 && left <= width;
- },
- onNewNoteMove(e) {
- if (!this.isMovingCurrentComment) return;
-
- const { deltaX, deltaY } = this.getMovingNotePositionDelta(e);
- const x = this.currentCommentForm.x + deltaX;
- const y = this.currentCommentForm.y + deltaY;
-
- const movingNoteNewPosition = {
- x,
- y,
- width: this.dimensions.width,
- height: this.dimensions.height,
- };
-
- if (!this.isPositionInOverlay(movingNoteNewPosition)) {
- this.onNewNoteMouseup();
- return;
- }
-
- this.movingNoteNewPosition = movingNoteNewPosition;
- },
- onExistingNoteMove(e) {
- const note = this.notes.find(({ id }) => id === this.movingNoteStartPosition.noteId);
- if (!note || !this.canMoveNote(note)) return;
-
- const { position } = note;
- const { width, height } = position;
- const widthRatio = this.dimensions.width / width;
- const heightRatio = this.dimensions.height / height;
-
- const { deltaX, deltaY } = this.getMovingNotePositionDelta(e);
- const x = position.x * widthRatio + deltaX;
- const y = position.y * heightRatio + deltaY;
-
- const movingNoteNewPosition = {
- x,
- y,
- width: this.dimensions.width,
- height: this.dimensions.height,
- };
-
- if (!this.isPositionInOverlay(movingNoteNewPosition)) {
- this.onExistingNoteMouseup();
- return;
- }
-
- this.movingNoteNewPosition = movingNoteNewPosition;
- },
- onNewNoteMouseup() {
- if (!this.movingNoteNewPosition) return;
-
- const { x, y } = this.movingNoteNewPosition;
- this.setNewNoteCoordinates({ x, y });
- },
- onExistingNoteMouseup(note) {
- if (!this.movingNoteStartPosition || !this.movingNoteNewPosition) {
- this.updateActiveDiscussion(note.id);
- this.$emit('closeCommentForm');
- return;
- }
-
- const { x, y } = this.movingNoteNewPosition;
- this.$emit('moveNote', {
- noteId: this.movingNoteStartPosition.noteId,
- discussionId: this.movingNoteStartPosition.discussionId,
- coordinates: { x, y },
- });
- },
- onNoteMousedown({ clientX, clientY }, note) {
- this.movingNoteStartPosition = {
- noteId: note?.id,
- discussionId: note?.discussion.id,
- clientX,
- clientY,
- };
- },
- onOverlayMousemove(e) {
- if (!this.movingNoteStartPosition) return;
-
- if (this.isMovingCurrentComment) {
- this.onNewNoteMove(e);
- } else {
- this.onExistingNoteMove(e);
- }
- },
- onNoteMouseup(note) {
- if (!this.movingNoteStartPosition) return;
-
- if (this.isMovingCurrentComment) {
- this.onNewNoteMouseup();
- } else {
- this.onExistingNoteMouseup(note);
- }
-
- this.movingNoteStartPosition = null;
- this.movingNoteNewPosition = null;
- },
- onAddCommentMouseup({ offsetX, offsetY }) {
- if (this.disableCommenting) return;
- if (this.activeDiscussion.id) {
- this.updateActiveDiscussion();
- }
-
- this.setNewNoteCoordinates({ x: offsetX, y: offsetY });
- },
- updateActiveDiscussion(id) {
- this.$apollo.mutate({
- mutation: updateActiveDiscussionMutation,
- variables: {
- id,
- source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin,
- },
- });
- },
- isNoteInactive(note) {
- return this.activeDiscussion.id && this.activeDiscussion.id !== note.id;
- },
- designPinClass(note) {
- return { inactive: this.isNoteInactive(note), resolved: note.resolved };
- },
- },
-};
-</script>
-
-<template>
- <div
- class="position-absolute image-diff-overlay frame"
- :style="overlayStyle"
- @mousemove="onOverlayMousemove"
- @mouseleave="onNoteMouseup"
- >
- <button
- v-show="!disableCommenting"
- type="button"
- class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button"
- data-qa-selector="design_image_button"
- @mouseup="onAddCommentMouseup"
- ></button>
- <template v-for="note in notes">
- <design-note-pin
- v-if="resolvedDiscussionsExpanded || !note.resolved"
- :key="note.id"
- :label="note.index"
- :repositioning="isMovingNote(note.id)"
- :position="
- isMovingNote(note.id) && movingNoteNewPosition
- ? getNotePositionStyle(movingNoteNewPosition)
- : getNotePositionStyle(note.position)
- "
- :class="designPinClass(note)"
- @mousedown.stop="onNoteMousedown($event, note)"
- @mouseup.stop="onNoteMouseup(note)"
- />
- </template>
-
- <design-note-pin
- v-if="currentCommentForm"
- :position="currentCommentPositionStyle"
- :repositioning="isMovingCurrentComment"
- @mousedown.stop="onNoteMousedown"
- @mouseup.stop="onNoteMouseup"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_presentation.vue b/app/assets/javascripts/design_management_legacy/components/design_presentation.vue
deleted file mode 100644
index 84dbb2809d9..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_presentation.vue
+++ /dev/null
@@ -1,322 +0,0 @@
-<script>
-import { throttle } from 'lodash';
-import DesignImage from './image.vue';
-import DesignOverlay from './design_overlay.vue';
-
-const CLICK_DRAG_BUFFER_PX = 2;
-
-export default {
- components: {
- DesignImage,
- DesignOverlay,
- },
- props: {
- image: {
- type: String,
- required: false,
- default: '',
- },
- imageName: {
- type: String,
- required: false,
- default: '',
- },
- discussions: {
- type: Array,
- required: true,
- },
- isAnnotating: {
- type: Boolean,
- required: false,
- default: false,
- },
- scale: {
- type: Number,
- required: false,
- default: 1,
- },
- resolvedDiscussionsExpanded: {
- type: Boolean,
- required: true,
- },
- },
- data() {
- return {
- overlayDimensions: null,
- overlayPosition: null,
- currentAnnotationPosition: null,
- zoomFocalPoint: {
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- },
- initialLoad: true,
- lastDragPosition: null,
- isDraggingDesign: false,
- };
- },
- computed: {
- discussionStartingNotes() {
- return this.discussions.map(discussion => ({
- ...discussion.notes[0],
- index: discussion.index,
- }));
- },
- currentCommentForm() {
- return (this.isAnnotating && this.currentAnnotationPosition) || null;
- },
- presentationStyle() {
- return {
- cursor: this.isDraggingDesign ? 'grabbing' : undefined,
- };
- },
- },
- beforeDestroy() {
- const { presentationViewport } = this.$refs;
- if (!presentationViewport) return;
-
- presentationViewport.removeEventListener('scroll', this.scrollThrottled, false);
- },
- mounted() {
- const { presentationViewport } = this.$refs;
- if (!presentationViewport) return;
-
- this.scrollThrottled = throttle(() => {
- this.shiftZoomFocalPoint();
- }, 400);
-
- presentationViewport.addEventListener('scroll', this.scrollThrottled, false);
- },
- methods: {
- syncCurrentAnnotationPosition() {
- if (!this.currentAnnotationPosition) return;
-
- const widthRatio = this.overlayDimensions.width / this.currentAnnotationPosition.width;
- const heightRatio = this.overlayDimensions.height / this.currentAnnotationPosition.height;
- const x = this.currentAnnotationPosition.x * widthRatio;
- const y = this.currentAnnotationPosition.y * heightRatio;
-
- this.currentAnnotationPosition = this.getAnnotationPositon({ x, y });
- },
- setOverlayDimensions(overlayDimensions) {
- this.overlayDimensions = overlayDimensions;
-
- // every time we set overlay dimensions, we need to
- // update the current annotation as well
- this.syncCurrentAnnotationPosition();
- },
- setOverlayPosition() {
- if (!this.overlayDimensions) {
- this.overlayPosition = {};
- }
-
- const { presentationViewport } = this.$refs;
- if (!presentationViewport) return;
-
- // default to center
- this.overlayPosition = {
- left: `calc(50% - ${this.overlayDimensions.width / 2}px)`,
- top: `calc(50% - ${this.overlayDimensions.height / 2}px)`,
- };
-
- // if the overlay overflows, then don't center
- if (this.overlayDimensions.width > presentationViewport.offsetWidth) {
- this.overlayPosition.left = '0';
- }
- if (this.overlayDimensions.height > presentationViewport.offsetHeight) {
- this.overlayPosition.top = '0';
- }
- },
- /**
- * Return a point that represents the center of an
- * overflowing child element w.r.t it's parent
- */
- getViewportCenter() {
- const { presentationViewport } = this.$refs;
- if (!presentationViewport) return {};
-
- // get height of scroll bars (i.e. the max values for scrollTop, scrollLeft)
- const scrollBarWidth = presentationViewport.scrollWidth - presentationViewport.offsetWidth;
- const scrollBarHeight = presentationViewport.scrollHeight - presentationViewport.offsetHeight;
-
- // determine how many child pixels have been scrolled
- const xScrollRatio =
- presentationViewport.scrollLeft > 0 ? presentationViewport.scrollLeft / scrollBarWidth : 0;
- const yScrollRatio =
- presentationViewport.scrollTop > 0 ? presentationViewport.scrollTop / scrollBarHeight : 0;
- const xScrollOffset =
- (presentationViewport.scrollWidth - presentationViewport.offsetWidth - 0) * xScrollRatio;
- const yScrollOffset =
- (presentationViewport.scrollHeight - presentationViewport.offsetHeight - 0) * yScrollRatio;
-
- const viewportCenterX = presentationViewport.offsetWidth / 2;
- const viewportCenterY = presentationViewport.offsetHeight / 2;
- const focalPointX = viewportCenterX + xScrollOffset;
- const focalPointY = viewportCenterY + yScrollOffset;
-
- return {
- x: focalPointX,
- y: focalPointY,
- };
- },
- /**
- * Scroll the viewport such that the focal point is positioned centrally
- */
- scrollToFocalPoint() {
- const { presentationViewport } = this.$refs;
- if (!presentationViewport) return;
-
- const scrollX = this.zoomFocalPoint.x - presentationViewport.offsetWidth / 2;
- const scrollY = this.zoomFocalPoint.y - presentationViewport.offsetHeight / 2;
-
- presentationViewport.scrollTo(scrollX, scrollY);
- },
- scaleZoomFocalPoint() {
- const { x, y, width, height } = this.zoomFocalPoint;
- const widthRatio = this.overlayDimensions.width / width;
- const heightRatio = this.overlayDimensions.height / height;
-
- this.zoomFocalPoint = {
- x: Math.round(x * widthRatio * 100) / 100,
- y: Math.round(y * heightRatio * 100) / 100,
- ...this.overlayDimensions,
- };
- },
- shiftZoomFocalPoint() {
- this.zoomFocalPoint = {
- ...this.getViewportCenter(),
- ...this.overlayDimensions,
- };
- },
- onImageResize(imageDimensions) {
- this.setOverlayDimensions(imageDimensions);
- this.setOverlayPosition();
-
- this.$nextTick(() => {
- if (this.initialLoad) {
- // set focal point on initial load
- this.shiftZoomFocalPoint();
- this.initialLoad = false;
- } else {
- this.scaleZoomFocalPoint();
- this.scrollToFocalPoint();
- }
- });
- },
- getAnnotationPositon(coordinates) {
- const { x, y } = coordinates;
- const { width, height } = this.overlayDimensions;
- return {
- x: Math.round(x),
- y: Math.round(y),
- width: Math.round(width),
- height: Math.round(height),
- };
- },
- openCommentForm(coordinates) {
- this.currentAnnotationPosition = this.getAnnotationPositon(coordinates);
- this.$emit('openCommentForm', this.currentAnnotationPosition);
- },
- closeCommentForm() {
- this.currentAnnotationPosition = null;
- this.$emit('closeCommentForm');
- },
- moveNote({ noteId, discussionId, coordinates }) {
- const position = this.getAnnotationPositon(coordinates);
- this.$emit('moveNote', { noteId, discussionId, position });
- },
- onPresentationMousedown({ clientX, clientY }) {
- if (!this.isDesignOverflowing()) return;
-
- this.lastDragPosition = {
- x: clientX,
- y: clientY,
- };
- },
- getDragDelta(clientX, clientY) {
- return {
- deltaX: this.lastDragPosition.x - clientX,
- deltaY: this.lastDragPosition.y - clientY,
- };
- },
- exceedsDragThreshold(clientX, clientY) {
- const { deltaX, deltaY } = this.getDragDelta(clientX, clientY);
-
- return Math.abs(deltaX) > CLICK_DRAG_BUFFER_PX || Math.abs(deltaY) > CLICK_DRAG_BUFFER_PX;
- },
- shouldDragDesign(clientX, clientY) {
- return (
- this.lastDragPosition &&
- (this.isDraggingDesign || this.exceedsDragThreshold(clientX, clientY))
- );
- },
- onPresentationMousemove({ clientX, clientY }) {
- const { presentationViewport } = this.$refs;
- if (!presentationViewport || !this.shouldDragDesign(clientX, clientY)) return;
-
- this.isDraggingDesign = true;
-
- const { scrollLeft, scrollTop } = presentationViewport;
- const { deltaX, deltaY } = this.getDragDelta(clientX, clientY);
- presentationViewport.scrollTo(scrollLeft + deltaX, scrollTop + deltaY);
-
- this.lastDragPosition = {
- x: clientX,
- y: clientY,
- };
- },
- onPresentationMouseup() {
- this.lastDragPosition = null;
- this.isDraggingDesign = false;
- },
- isDesignOverflowing() {
- const { presentationViewport } = this.$refs;
- if (!presentationViewport) return false;
-
- return (
- presentationViewport.scrollWidth > presentationViewport.offsetWidth ||
- presentationViewport.scrollHeight > presentationViewport.offsetHeight
- );
- },
- },
-};
-</script>
-
-<template>
- <div
- ref="presentationViewport"
- class="h-100 w-100 p-3 overflow-auto position-relative"
- :style="presentationStyle"
- @mousedown="onPresentationMousedown"
- @mousemove="onPresentationMousemove"
- @mouseup="onPresentationMouseup"
- @mouseleave="onPresentationMouseup"
- @touchstart="onPresentationMousedown"
- @touchmove="onPresentationMousemove"
- @touchend="onPresentationMouseup"
- @touchcancel="onPresentationMouseup"
- >
- <div class="h-100 w-100 d-flex align-items-center position-relative">
- <design-image
- v-if="image"
- :image="image"
- :name="imageName"
- :scale="scale"
- @resize="onImageResize"
- />
- <design-overlay
- v-if="overlayDimensions && overlayPosition"
- :dimensions="overlayDimensions"
- :position="overlayPosition"
- :notes="discussionStartingNotes"
- :current-comment-form="currentCommentForm"
- :disable-commenting="isDraggingDesign"
- :resolved-discussions-expanded="resolvedDiscussionsExpanded"
- @openCommentForm="openCommentForm"
- @closeCommentForm="closeCommentForm"
- @moveNote="moveNote"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_scaler.vue b/app/assets/javascripts/design_management_legacy/components/design_scaler.vue
deleted file mode 100644
index 55dee74bef5..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_scaler.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-
-const SCALE_STEP_SIZE = 0.2;
-const DEFAULT_SCALE = 1;
-const MIN_SCALE = 1;
-const MAX_SCALE = 2;
-
-export default {
- components: {
- GlIcon,
- },
- data() {
- return {
- scale: DEFAULT_SCALE,
- };
- },
- computed: {
- disableReset() {
- return this.scale <= MIN_SCALE;
- },
- disableDecrease() {
- return this.scale === DEFAULT_SCALE;
- },
- disableIncrease() {
- return this.scale >= MAX_SCALE;
- },
- },
- methods: {
- setScale(scale) {
- if (scale < MIN_SCALE) {
- return;
- }
-
- this.scale = Math.round(scale * 100) / 100;
- this.$emit('scale', this.scale);
- },
- incrementScale() {
- this.setScale(this.scale + SCALE_STEP_SIZE);
- },
- decrementScale() {
- this.setScale(this.scale - SCALE_STEP_SIZE);
- },
- resetScale() {
- this.setScale(DEFAULT_SCALE);
- },
- },
-};
-</script>
-
-<template>
- <div class="design-scaler btn-group" role="group">
- <button class="btn" :disabled="disableDecrease" @click="decrementScale">
- <span class="d-flex-center gl-icon s16">
- –
- </span>
- </button>
- <button class="btn" :disabled="disableReset" @click="resetScale">
- <gl-icon name="redo" />
- </button>
- <button class="btn" :disabled="disableIncrease" @click="incrementScale">
- <gl-icon name="plus" />
- </button>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/design_sidebar.vue b/app/assets/javascripts/design_management_legacy/components/design_sidebar.vue
deleted file mode 100644
index 622120e2008..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/design_sidebar.vue
+++ /dev/null
@@ -1,178 +0,0 @@
-<script>
-import Cookies from 'js-cookie';
-import { GlCollapse, GlButton, GlPopover } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
-import { extractDiscussions, extractParticipants } from '../utils/design_management_utils';
-import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
-import DesignDiscussion from './design_notes/design_discussion.vue';
-import Participants from '~/sidebar/components/participants/participants.vue';
-
-export default {
- components: {
- DesignDiscussion,
- Participants,
- GlCollapse,
- GlButton,
- GlPopover,
- },
- props: {
- design: {
- type: Object,
- required: true,
- },
- resolvedDiscussionsExpanded: {
- type: Boolean,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- isResolvedCommentsPopoverHidden: parseBoolean(Cookies.get(this.$options.cookieKey)),
- discussionWithOpenForm: '',
- };
- },
- computed: {
- discussions() {
- return extractDiscussions(this.design.discussions);
- },
- issue() {
- return {
- ...this.design.issue,
- webPath: this.design.issue.webPath.substr(1),
- };
- },
- discussionParticipants() {
- return extractParticipants(this.issue.participants);
- },
- resolvedDiscussions() {
- return this.discussions.filter(discussion => discussion.resolved);
- },
- unresolvedDiscussions() {
- return this.discussions.filter(discussion => !discussion.resolved);
- },
- resolvedCommentsToggleIcon() {
- return this.resolvedDiscussionsExpanded ? 'chevron-down' : 'chevron-right';
- },
- },
- methods: {
- handleSidebarClick() {
- this.isResolvedCommentsPopoverHidden = true;
- Cookies.set(this.$options.cookieKey, 'true', { expires: 365 * 10 });
- this.updateActiveDiscussion();
- },
- updateActiveDiscussion(id) {
- this.$apollo.mutate({
- mutation: updateActiveDiscussionMutation,
- variables: {
- id,
- source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion,
- },
- });
- },
- closeCommentForm() {
- this.comment = '';
- this.$emit('closeCommentForm');
- },
- updateDiscussionWithOpenForm(id) {
- this.discussionWithOpenForm = id;
- },
- },
- resolveCommentsToggleText: s__('DesignManagement|Resolved Comments'),
- cookieKey: 'hide_design_resolved_comments_popover',
-};
-</script>
-
-<template>
- <div class="image-notes" @click="handleSidebarClick">
- <h2 class="gl-font-weight-bold gl-mt-0">
- {{ issue.title }}
- </h2>
- <a
- class="gl-text-gray-400 gl-text-decoration-none gl-mb-6 gl-display-block"
- :href="issue.webUrl"
- >{{ issue.webPath }}</a
- >
- <participants
- :participants="discussionParticipants"
- :show-participant-label="false"
- class="gl-mb-4"
- />
- <h2
- v-if="unresolvedDiscussions.length === 0"
- class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
- data-testid="new-discussion-disclaimer"
- >
- {{ s__("DesignManagement|Click the image where you'd like to start a new discussion") }}
- </h2>
- <design-discussion
- v-for="discussion in unresolvedDiscussions"
- :key="discussion.id"
- :discussion="discussion"
- :design-id="$route.params.id"
- :noteable-id="design.id"
- :markdown-preview-path="markdownPreviewPath"
- :resolved-discussions-expanded="resolvedDiscussionsExpanded"
- :discussion-with-open-form="discussionWithOpenForm"
- data-testid="unresolved-discussion"
- @createNoteError="$emit('onDesignDiscussionError', $event)"
- @updateNoteError="$emit('updateNoteError', $event)"
- @resolveDiscussionError="$emit('resolveDiscussionError', $event)"
- @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
- @openForm="updateDiscussionWithOpenForm"
- />
- <template v-if="resolvedDiscussions.length > 0">
- <gl-button
- id="resolved-comments"
- data-testid="resolved-comments"
- :icon="resolvedCommentsToggleIcon"
- variant="link"
- class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4"
- @click="$emit('toggleResolvedComments')"
- >{{ $options.resolveCommentsToggleText }} ({{ resolvedDiscussions.length }})
- </gl-button>
- <gl-popover
- v-if="!isResolvedCommentsPopoverHidden"
- :show="!isResolvedCommentsPopoverHidden"
- target="resolved-comments"
- container="popovercontainer"
- placement="top"
- :title="s__('DesignManagement|Resolved Comments')"
- >
- <p>
- {{
- s__(
- 'DesignManagement|Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below',
- )
- }}
- </p>
- <a href="#" rel="noopener noreferrer" target="_blank">{{
- s__('DesignManagement|Learn more about resolving comments')
- }}</a>
- </gl-popover>
- <gl-collapse :visible="resolvedDiscussionsExpanded" class="gl-mt-3">
- <design-discussion
- v-for="discussion in resolvedDiscussions"
- :key="discussion.id"
- :discussion="discussion"
- :design-id="$route.params.id"
- :noteable-id="design.id"
- :markdown-preview-path="markdownPreviewPath"
- :resolved-discussions-expanded="resolvedDiscussionsExpanded"
- :discussion-with-open-form="discussionWithOpenForm"
- data-testid="resolved-discussion"
- @error="$emit('onDesignDiscussionError', $event)"
- @updateNoteError="$emit('updateNoteError', $event)"
- @openForm="updateDiscussionWithOpenForm"
- @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
- />
- </gl-collapse>
- </template>
- <slot name="replyForm"></slot>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/image.vue b/app/assets/javascripts/design_management_legacy/components/image.vue
deleted file mode 100644
index 91b7b576e0c..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/image.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<script>
-import { throttle } from 'lodash';
-import { GlIcon } from '@gitlab/ui';
-
-export default {
- components: {
- GlIcon,
- },
- props: {
- image: {
- type: String,
- required: false,
- default: '',
- },
- name: {
- type: String,
- required: false,
- default: '',
- },
- scale: {
- type: Number,
- required: false,
- default: 1,
- },
- },
- data() {
- return {
- baseImageSize: null,
- imageStyle: null,
- imageError: false,
- };
- },
- watch: {
- scale(val) {
- this.zoom(val);
- },
- },
- beforeDestroy() {
- window.removeEventListener('resize', this.resizeThrottled, false);
- },
- mounted() {
- this.onImgLoad();
-
- this.resizeThrottled = throttle(() => {
- // NOTE: if imageStyle is set, then baseImageSize
- // won't change due to resize. We must still emit a
- // `resize` event so that the parent can handle
- // resizes appropriately (e.g. for design_overlay)
- this.setBaseImageSize();
- }, 400);
- window.addEventListener('resize', this.resizeThrottled, false);
- },
- methods: {
- onImgLoad() {
- requestIdleCallback(this.setBaseImageSize, { timeout: 1000 });
- },
- onImgError() {
- this.imageError = true;
- },
- setBaseImageSize() {
- const { contentImg } = this.$refs;
- if (!contentImg || contentImg.offsetHeight === 0 || contentImg.offsetWidth === 0) return;
-
- this.baseImageSize = {
- height: contentImg.offsetHeight,
- width: contentImg.offsetWidth,
- };
- this.onResize({ width: this.baseImageSize.width, height: this.baseImageSize.height });
- },
- onResize({ width, height }) {
- this.$emit('resize', { width, height });
- },
- zoom(amount) {
- if (amount === 1) {
- this.imageStyle = null;
- this.$nextTick(() => {
- this.setBaseImageSize();
- });
- return;
- }
- const width = this.baseImageSize.width * amount;
- const height = this.baseImageSize.height * amount;
-
- this.imageStyle = {
- width: `${width}px`,
- height: `${height}px`,
- };
-
- this.onResize({ width, height });
- },
- },
-};
-</script>
-
-<template>
- <div class="m-auto js-design-image">
- <gl-icon v-if="imageError" class="text-secondary-100" name="media-broken" :size="48" />
- <img
- v-show="!imageError"
- ref="contentImg"
- class="mh-100"
- :src="image"
- :alt="name"
- :style="imageStyle"
- :class="{ 'img-fluid': !imageStyle }"
- @error="onImgError"
- @load="onImgLoad"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/list/item.vue b/app/assets/javascripts/design_management_legacy/components/list/item.vue
deleted file mode 100644
index 40040b25e51..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/list/item.vue
+++ /dev/null
@@ -1,172 +0,0 @@
-<script>
-import { GlLoadingIcon, GlIcon, GlIntersectionObserver } from '@gitlab/ui';
-import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
-import { n__, __ } from '~/locale';
-import { DESIGN_ROUTE_NAME } from '../../router/constants';
-
-export default {
- components: {
- GlLoadingIcon,
- GlIntersectionObserver,
- GlIcon,
- Timeago,
- },
- props: {
- id: {
- type: [Number, String],
- required: true,
- },
- event: {
- type: String,
- required: true,
- },
- notesCount: {
- type: Number,
- required: true,
- },
- image: {
- type: String,
- required: true,
- },
- filename: {
- type: String,
- required: true,
- },
- updatedAt: {
- type: String,
- required: false,
- default: null,
- },
- isUploading: {
- type: Boolean,
- required: false,
- default: true,
- },
- imageV432x230: {
- type: String,
- required: false,
- default: null,
- },
- },
- data() {
- return {
- imageLoading: true,
- imageError: false,
- wasInView: false,
- };
- },
- computed: {
- icon() {
- const normalizedEvent = this.event.toLowerCase();
- const icons = {
- creation: {
- name: 'file-addition-solid',
- classes: 'text-success-500',
- tooltip: __('Added in this version'),
- },
- modification: {
- name: 'file-modified-solid',
- classes: 'text-primary-500',
- tooltip: __('Modified in this version'),
- },
- deletion: {
- name: 'file-deletion-solid',
- classes: 'text-danger-500',
- tooltip: __('Deleted in this version'),
- },
- };
-
- return icons[normalizedEvent] ? icons[normalizedEvent] : {};
- },
- notesLabel() {
- return n__('%d comment', '%d comments', this.notesCount);
- },
- imageLink() {
- return this.wasInView ? this.imageV432x230 || this.image : '';
- },
- showLoadingSpinner() {
- return this.imageLoading || this.isUploading;
- },
- showImageErrorIcon() {
- return this.wasInView && this.imageError;
- },
- showImage() {
- return !this.showLoadingSpinner && !this.showImageErrorIcon;
- },
- },
- methods: {
- onImageLoad() {
- this.imageLoading = false;
- this.imageError = false;
- },
- onImageError() {
- this.imageLoading = false;
- this.imageError = true;
- },
- onAppear() {
- // do nothing if image has previously
- // been in view
- if (this.wasInView) {
- return;
- }
-
- this.wasInView = true;
- this.imageLoading = true;
- },
- },
- DESIGN_ROUTE_NAME,
-};
-</script>
-
-<template>
- <router-link
- :to="{
- name: $options.DESIGN_ROUTE_NAME,
- params: { id: filename },
- query: $route.query,
- }"
- class="card cursor-pointer text-plain js-design-list-item design-list-item"
- >
- <div class="card-body p-0 d-flex-center overflow-hidden position-relative">
- <div v-if="icon.name" data-testid="designEvent" class="design-event position-absolute">
- <span :title="icon.tooltip" :aria-label="icon.tooltip">
- <gl-icon :name="icon.name" :size="18" :class="icon.classes" />
- </span>
- </div>
- <gl-intersection-observer @appear="onAppear">
- <gl-loading-icon v-if="showLoadingSpinner" size="md" />
- <gl-icon
- v-else-if="showImageErrorIcon"
- name="media-broken"
- class="text-secondary"
- :size="32"
- />
- <img
- v-show="showImage"
- :src="imageLink"
- :alt="filename"
- class="block mx-auto mw-100 mh-100 design-img"
- data-qa-selector="design_image"
- @load="onImageLoad"
- @error="onImageError"
- />
- </gl-intersection-observer>
- </div>
- <div class="card-footer d-flex w-100">
- <div class="d-flex flex-column str-truncated-100">
- <span class="bold str-truncated-100" data-qa-selector="design_file_name">{{
- filename
- }}</span>
- <span v-if="updatedAt" class="str-truncated-100">
- {{ __('Updated') }} <timeago :time="updatedAt" tooltip-placement="bottom" />
- </span>
- </div>
- <div v-if="notesCount" class="ml-auto d-flex align-items-center text-secondary">
- <gl-icon name="comments" class="ml-1" />
- <span :aria-label="notesLabel" class="ml-1">
- {{ notesCount }}
- </span>
- </div>
- </div>
- </router-link>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/toolbar/index.vue b/app/assets/javascripts/design_management_legacy/components/toolbar/index.vue
deleted file mode 100644
index 5729072fe38..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/toolbar/index.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<script>
-import { GlDeprecatedButton, GlIcon } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
-import Pagination from './pagination.vue';
-import DeleteButton from '../delete_button.vue';
-import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql';
-import appDataQuery from '../../graphql/queries/app_data.query.graphql';
-import { DESIGNS_ROUTE_NAME } from '../../router/constants';
-
-export default {
- components: {
- GlIcon,
- Pagination,
- DeleteButton,
- GlDeprecatedButton,
- },
- mixins: [timeagoMixin],
- props: {
- id: {
- type: String,
- required: true,
- },
- isDeleting: {
- type: Boolean,
- required: true,
- },
- filename: {
- type: String,
- required: false,
- default: '',
- },
- updatedAt: {
- type: String,
- required: false,
- default: null,
- },
- updatedBy: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- isLatestVersion: {
- type: Boolean,
- required: true,
- },
- image: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- permissions: {
- createDesign: false,
- },
- projectPath: '',
- issueIid: null,
- };
- },
- apollo: {
- appData: {
- query: appDataQuery,
- manual: true,
- result({ data: { projectPath, issueIid } }) {
- this.projectPath = projectPath;
- this.issueIid = issueIid;
- },
- },
- permissions: {
- query: permissionsQuery,
- variables() {
- return {
- fullPath: this.projectPath,
- iid: this.issueIid,
- };
- },
- update: data => data.project.issue.userPermissions,
- },
- },
- computed: {
- updatedText() {
- return sprintf(__('Updated %{updated_at} by %{updated_by}'), {
- updated_at: this.timeFormatted(this.updatedAt),
- updated_by: this.updatedBy.name,
- });
- },
- canDeleteDesign() {
- return this.permissions.createDesign;
- },
- },
- DESIGNS_ROUTE_NAME,
-};
-</script>
-
-<template>
- <header class="d-flex p-2 bg-white align-items-center js-design-header">
- <router-link
- :to="{
- name: $options.DESIGNS_ROUTE_NAME,
- query: $route.query,
- }"
- :aria-label="s__('DesignManagement|Go back to designs')"
- class="mr-3 text-plain d-flex justify-content-center align-items-center"
- >
- <gl-icon :size="18" name="close" />
- </router-link>
- <div class="overflow-hidden d-flex align-items-center">
- <h2 class="m-0 str-truncated-100 gl-font-base">{{ filename }}</h2>
- <small v-if="updatedAt" class="text-secondary">{{ updatedText }}</small>
- </div>
- <pagination :id="id" class="ml-auto flex-shrink-0" />
- <gl-deprecated-button :href="image" class="mr-2">
- <gl-icon :size="18" name="download" />
- </gl-deprecated-button>
- <delete-button
- v-if="isLatestVersion && canDeleteDesign"
- :is-deleting="isDeleting"
- button-variant="danger"
- @deleteSelectedDesigns="$emit('delete')"
- >
- <gl-icon :size="18" name="remove" />
- </delete-button>
- </header>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/toolbar/pagination.vue b/app/assets/javascripts/design_management_legacy/components/toolbar/pagination.vue
deleted file mode 100644
index bf62a8f66a6..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/toolbar/pagination.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script>
-/* global Mousetrap */
-import 'mousetrap';
-import { s__, sprintf } from '~/locale';
-import PaginationButton from './pagination_button.vue';
-import allDesignsMixin from '../../mixins/all_designs';
-import { DESIGN_ROUTE_NAME } from '../../router/constants';
-
-export default {
- components: {
- PaginationButton,
- },
- mixins: [allDesignsMixin],
- props: {
- id: {
- type: String,
- required: true,
- },
- },
- computed: {
- designsCount() {
- return this.designs.length;
- },
- currentIndex() {
- return this.designs.findIndex(design => design.filename === this.id);
- },
- paginationText() {
- return sprintf(s__('DesignManagement|%{current_design} of %{designs_count}'), {
- current_design: this.currentIndex + 1,
- designs_count: this.designsCount,
- });
- },
- previousDesign() {
- if (!this.designsCount) return null;
-
- return this.designs[this.currentIndex - 1];
- },
- nextDesign() {
- if (!this.designsCount) return null;
-
- return this.designs[this.currentIndex + 1];
- },
- },
- mounted() {
- Mousetrap.bind('left', () => this.navigateToDesign(this.previousDesign));
- Mousetrap.bind('right', () => this.navigateToDesign(this.nextDesign));
- },
- beforeDestroy() {
- Mousetrap.unbind(['left', 'right'], this.navigateToDesign);
- },
- methods: {
- navigateToDesign(design) {
- if (design) {
- this.$router.push({
- name: DESIGN_ROUTE_NAME,
- params: { id: design.filename },
- query: this.$route.query,
- });
- }
- },
- },
-};
-</script>
-
-<template>
- <div v-if="designsCount" class="d-flex align-items-center">
- {{ paginationText }}
- <div class="btn-group ml-3 mr-3">
- <pagination-button
- :design="previousDesign"
- :title="s__('DesignManagement|Go to previous design')"
- icon-name="angle-left"
- class="js-previous-design"
- />
- <pagination-button
- :design="nextDesign"
- :title="s__('DesignManagement|Go to next design')"
- icon-name="angle-right"
- class="js-next-design"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/toolbar/pagination_button.vue b/app/assets/javascripts/design_management_legacy/components/toolbar/pagination_button.vue
deleted file mode 100644
index 7051aaddd04..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/toolbar/pagination_button.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import { DESIGN_ROUTE_NAME } from '../../router/constants';
-
-export default {
- components: {
- GlIcon,
- },
- props: {
- design: {
- type: Object,
- required: false,
- default: null,
- },
- title: {
- type: String,
- required: true,
- },
- iconName: {
- type: String,
- required: true,
- },
- },
- computed: {
- designLink() {
- if (!this.design) return {};
-
- return {
- name: DESIGN_ROUTE_NAME,
- params: { id: this.design.filename },
- query: this.$route.query,
- };
- },
- },
-};
-</script>
-
-<template>
- <router-link
- :to="designLink"
- :disabled="!design"
- :class="{ disabled: !design }"
- :aria-label="title"
- class="btn btn-default"
- >
- <gl-icon :name="iconName" />
- </router-link>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/upload/button.vue b/app/assets/javascripts/design_management_legacy/components/upload/button.vue
deleted file mode 100644
index 68555104a3c..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/upload/button.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<script>
-import { GlDeprecatedButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
-import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
-
-export default {
- components: {
- GlDeprecatedButton,
- GlLoadingIcon,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- isSaving: {
- type: Boolean,
- required: true,
- },
- },
- methods: {
- openFileUpload() {
- this.$refs.fileUpload.click();
- },
- onFileUploadChange(e) {
- this.$emit('upload', e.target.files);
- },
- },
- VALID_DESIGN_FILE_MIMETYPE,
-};
-</script>
-
-<template>
- <div>
- <gl-deprecated-button
- v-gl-tooltip.hover
- :title="
- s__(
- 'DesignManagement|Adding a design with the same filename replaces the file in a new version.',
- )
- "
- :disabled="isSaving"
- variant="success"
- @click="openFileUpload"
- >
- {{ s__('DesignManagement|Upload designs') }}
- <gl-loading-icon v-if="isSaving" inline class="ml-1" />
- </gl-deprecated-button>
-
- <input
- ref="fileUpload"
- type="file"
- name="design_file"
- :accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype"
- class="hide"
- multiple
- @change="onFileUploadChange"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/upload/design_dropzone.vue b/app/assets/javascripts/design_management_legacy/components/upload/design_dropzone.vue
deleted file mode 100644
index e435c84c959..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/upload/design_dropzone.vue
+++ /dev/null
@@ -1,134 +0,0 @@
-<script>
-import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import uploadDesignMutation from '../../graphql/mutations/upload_design.mutation.graphql';
-import { UPLOAD_DESIGN_INVALID_FILETYPE_ERROR } from '../../utils/error_messages';
-import { isValidDesignFile } from '../../utils/design_management_utils';
-import { VALID_DATA_TRANSFER_TYPE, VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
-
-export default {
- components: {
- GlIcon,
- GlLink,
- GlSprintf,
- },
- data() {
- return {
- dragCounter: 0,
- isDragDataValid: false,
- };
- },
- computed: {
- dragging() {
- return this.dragCounter !== 0;
- },
- },
- methods: {
- isValidUpload(files) {
- return files.every(isValidDesignFile);
- },
- isValidDragDataType({ dataTransfer }) {
- return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE));
- },
- ondrop({ dataTransfer = {} }) {
- this.dragCounter = 0;
- // User already had feedback when dropzone was active, so bail here
- if (!this.isDragDataValid) {
- return;
- }
-
- const { files } = dataTransfer;
- if (!this.isValidUpload(Array.from(files))) {
- createFlash(UPLOAD_DESIGN_INVALID_FILETYPE_ERROR);
- return;
- }
-
- this.$emit('change', files);
- },
- ondragenter(e) {
- this.dragCounter += 1;
- this.isDragDataValid = this.isValidDragDataType(e);
- },
- ondragleave() {
- this.dragCounter -= 1;
- },
- openFileUpload() {
- this.$refs.fileUpload.click();
- },
- onDesignInputChange(e) {
- this.$emit('change', e.target.files);
- },
- },
- uploadDesignMutation,
- VALID_DESIGN_FILE_MIMETYPE,
-};
-</script>
-
-<template>
- <div
- class="w-100 position-relative"
- @dragstart.prevent.stop
- @dragend.prevent.stop
- @dragover.prevent.stop
- @dragenter.prevent.stop="ondragenter"
- @dragleave.prevent.stop="ondragleave"
- @drop.prevent.stop="ondrop"
- >
- <slot>
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- @click="openFileUpload"
- >
- <div class="d-flex-center flex-column text-center">
- <gl-icon name="doc-new" :size="48" class="mb-4" />
- <p>
- <gl-sprintf
- :message="
- __(
- '%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}.',
- )
- "
- >
- <template #lineOne="{ content }"
- ><span class="d-block">{{ content }}</span>
- </template>
-
- <template #link="{ content }">
- <gl-link class="h-100 w-100" @click.stop="openFileUpload">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- </button>
-
- <input
- ref="fileUpload"
- type="file"
- name="design_file"
- :accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype"
- class="hide"
- multiple
- @change="onDesignInputChange"
- />
- </slot>
- <transition name="design-dropzone-fade">
- <div
- v-show="dragging"
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- >
- <div v-show="!isDragDataValid" class="mw-50 text-center">
- <h3>{{ __('Oh no!') }}</h3>
- <span>{{
- __(
- 'You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.',
- )
- }}</span>
- </div>
- <div v-show="isDragDataValid" class="mw-50 text-center">
- <h3>{{ __('Incoming!') }}</h3>
- <span>{{ __('Drop your designs to start your upload.') }}</span>
- </div>
- </div>
- </transition>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/components/upload/design_version_dropdown.vue b/app/assets/javascripts/design_management_legacy/components/upload/design_version_dropdown.vue
deleted file mode 100644
index 879d2523848..00000000000
--- a/app/assets/javascripts/design_management_legacy/components/upload/design_version_dropdown.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<script>
-import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
-import allVersionsMixin from '../../mixins/all_versions';
-import { findVersionId } from '../../utils/design_management_utils';
-
-export default {
- components: {
- GlDeprecatedDropdown,
- GlDeprecatedDropdownItem,
- },
- mixins: [allVersionsMixin],
- computed: {
- queryVersion() {
- return this.$route.query.version;
- },
- currentVersionIdx() {
- if (!this.queryVersion) return 0;
-
- const idx = this.allVersions.findIndex(
- version => this.findVersionId(version.node.id) === this.queryVersion,
- );
-
- // if the currentVersionId isn't a valid version (i.e. not in allVersions)
- // then return the latest version (index 0)
- return idx !== -1 ? idx : 0;
- },
- currentVersionId() {
- if (this.queryVersion) return this.queryVersion;
-
- const currentVersion = this.allVersions[this.currentVersionIdx];
- return this.findVersionId(currentVersion.node.id);
- },
- dropdownText() {
- if (this.isLatestVersion) {
- return __('Showing Latest Version');
- }
- // allVersions is sorted in reverse chronological order (latest first)
- const currentVersionNumber = this.allVersions.length - this.currentVersionIdx;
-
- return sprintf(__('Showing Version #%{versionNumber}'), {
- versionNumber: currentVersionNumber,
- });
- },
- },
- methods: {
- findVersionId,
- },
-};
-</script>
-
-<template>
- <gl-deprecated-dropdown :text="dropdownText" variant="link" class="design-version-dropdown">
- <gl-deprecated-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
- <router-link
- class="d-flex js-version-link"
- :to="{ path: $route.path, query: { version: findVersionId(version.node.id) } }"
- >
- <div class="flex-grow-1 ml-2">
- <div>
- <strong
- >{{ __('Version') }} {{ allVersions.length - index }}
- <span v-if="findVersionId(version.node.id) === latestVersionId"
- >({{ __('latest') }})</span
- >
- </strong>
- </div>
- </div>
- <i
- v-if="findVersionId(version.node.id) === currentVersionId"
- class="fa fa-check float-right gl-mr-2"
- ></i>
- </router-link>
- </gl-deprecated-dropdown-item>
- </gl-deprecated-dropdown>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/constants.js b/app/assets/javascripts/design_management_legacy/constants.js
deleted file mode 100644
index 21ff361a277..00000000000
--- a/app/assets/javascripts/design_management_legacy/constants.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// WARNING: replace this with something
-// more sensical as per https://gitlab.com/gitlab-org/gitlab/issues/118611
-export const VALID_DESIGN_FILE_MIMETYPE = {
- mimetype: 'image/*',
- regex: /image\/.+/,
-};
-
-// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
-export const VALID_DATA_TRANSFER_TYPE = 'Files';
-
-export const ACTIVE_DISCUSSION_SOURCE_TYPES = {
- pin: 'pin',
- discussion: 'discussion',
-};
-
-export const DESIGN_DETAIL_LAYOUT_CLASSLIST = ['design-detail-layout', 'overflow-hidden', 'm-0'];
diff --git a/app/assets/javascripts/design_management_legacy/graphql.js b/app/assets/javascripts/design_management_legacy/graphql.js
deleted file mode 100644
index fae337aa75b..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import { uniqueId } from 'lodash';
-import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
-import createDefaultClient from '~/lib/graphql';
-import activeDiscussionQuery from './graphql/queries/active_discussion.query.graphql';
-import typeDefs from './graphql/typedefs.graphql';
-
-Vue.use(VueApollo);
-
-const resolvers = {
- Mutation: {
- updateActiveDiscussion: (_, { id = null, source }, { cache }) => {
- const data = cache.readQuery({ query: activeDiscussionQuery });
- data.activeDiscussion = {
- __typename: 'ActiveDiscussion',
- id,
- source,
- };
- cache.writeQuery({ query: activeDiscussionQuery, data });
- },
- },
-};
-
-const defaultClient = createDefaultClient(
- resolvers,
- // This config is added temporarily to resolve an issue with duplicate design IDs.
- // Should be removed as soon as https://gitlab.com/gitlab-org/gitlab/issues/13495 is resolved
- {
- cacheConfig: {
- dataIdFromObject: object => {
- // eslint-disable-next-line no-underscore-dangle, @gitlab/require-i18n-strings
- if (object.__typename === 'Design') {
- return object.id && object.image ? `${object.id}-${object.image}` : uniqueId();
- }
- return defaultDataIdFromObject(object);
- },
- },
- typeDefs,
- },
-);
-
-export default new VueApollo({
- defaultClient,
-});
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/design.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/design.fragment.graphql
deleted file mode 100644
index 4b1703e41c3..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/design.fragment.graphql
+++ /dev/null
@@ -1,24 +0,0 @@
-#import "./design_note.fragment.graphql"
-#import "./design_list.fragment.graphql"
-#import "./diff_refs.fragment.graphql"
-#import "./discussion_resolved_status.fragment.graphql"
-
-fragment DesignItem on Design {
- ...DesignListItem
- fullPath
- diffRefs {
- ...DesignDiffRefs
- }
- discussions {
- nodes {
- id
- replyId
- ...ResolvedStatus
- notes {
- nodes {
- ...DesignNote
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/design_list.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/design_list.fragment.graphql
deleted file mode 100644
index bc3132f9b42..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/design_list.fragment.graphql
+++ /dev/null
@@ -1,8 +0,0 @@
-fragment DesignListItem on Design {
- id
- event
- filename
- notesCount
- image
- imageV432x230
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/design_note.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/design_note.fragment.graphql
deleted file mode 100644
index 26edd2c0be1..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/design_note.fragment.graphql
+++ /dev/null
@@ -1,29 +0,0 @@
-#import "./diff_refs.fragment.graphql"
-#import "~/graphql_shared/fragments/author.fragment.graphql"
-#import "./note_permissions.fragment.graphql"
-
-fragment DesignNote on Note {
- id
- author {
- ...Author
- }
- body
- bodyHtml
- createdAt
- resolved
- position {
- diffRefs {
- ...DesignDiffRefs
- }
- x
- y
- height
- width
- }
- userPermissions {
- ...DesignNotePermissions
- }
- discussion {
- id
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/diff_refs.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/diff_refs.fragment.graphql
deleted file mode 100644
index 984a55814b0..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/diff_refs.fragment.graphql
+++ /dev/null
@@ -1,5 +0,0 @@
-fragment DesignDiffRefs on DiffRefs {
- baseSha
- startSha
- headSha
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/discussion_resolved_status.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/discussion_resolved_status.fragment.graphql
deleted file mode 100644
index 7483b508721..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/discussion_resolved_status.fragment.graphql
+++ /dev/null
@@ -1,9 +0,0 @@
-fragment ResolvedStatus on Discussion {
- resolvable
- resolved
- resolvedAt
- resolvedBy {
- name
- webUrl
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/note_permissions.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/note_permissions.fragment.graphql
deleted file mode 100644
index c243e39f3d3..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/note_permissions.fragment.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-fragment DesignNotePermissions on NotePermissions {
- adminNote
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/fragments/version.fragment.graphql b/app/assets/javascripts/design_management_legacy/graphql/fragments/version.fragment.graphql
deleted file mode 100644
index 7eb40b12f51..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/fragments/version.fragment.graphql
+++ /dev/null
@@ -1,4 +0,0 @@
-fragment VersionListItem on DesignVersion {
- id
- sha
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/create_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/create_image_diff_note.mutation.graphql
deleted file mode 100644
index c8ade328120..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/create_image_diff_note.mutation.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "../fragments/design_note.fragment.graphql"
-
-mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
- createImageDiffNote(input: $input) {
- note {
- ...DesignNote
- discussion {
- id
- replyId
- notes {
- edges {
- node {
- ...DesignNote
- }
- }
- }
- }
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/create_note.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/create_note.mutation.graphql
deleted file mode 100644
index 184ee6955dc..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/create_note.mutation.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "../fragments/design_note.fragment.graphql"
-
-mutation createNote($input: CreateNoteInput!) {
- createNote(input: $input) {
- note {
- ...DesignNote
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/destroy_design.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/destroy_design.mutation.graphql
deleted file mode 100644
index 0b3cf636cdb..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/destroy_design.mutation.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "../fragments/version.fragment.graphql"
-
-mutation destroyDesign($filenames: [String!]!, $projectPath: ID!, $iid: ID!) {
- designManagementDelete(input: { projectPath: $projectPath, iid: $iid, filenames: $filenames }) {
- version {
- ...VersionListItem
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/toggle_resolve_discussion.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/toggle_resolve_discussion.mutation.graphql
deleted file mode 100644
index 1157fc05d5f..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/toggle_resolve_discussion.mutation.graphql
+++ /dev/null
@@ -1,17 +0,0 @@
-#import "../fragments/design_note.fragment.graphql"
-#import "../fragments/discussion_resolved_status.fragment.graphql"
-
-mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) {
- discussionToggleResolve(input: { id: $id, resolve: $resolve }) {
- discussion {
- id
- ...ResolvedStatus
- notes {
- nodes {
- ...DesignNote
- }
- }
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql
deleted file mode 100644
index a24b6737159..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-mutation updateActiveDiscussion($id: String, $source: String) {
- updateActiveDiscussion(id: $id, source: $source) @client
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/update_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/update_image_diff_note.mutation.graphql
deleted file mode 100644
index 5562ca9d89f..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/update_image_diff_note.mutation.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "../fragments/design_note.fragment.graphql"
-
-mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) {
- updateImageDiffNote(input: $input) {
- errors
- note {
- ...DesignNote
- }
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/update_note.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/update_note.mutation.graphql
deleted file mode 100644
index b995e99fb6a..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/update_note.mutation.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "../fragments/design_note.fragment.graphql"
-
-mutation updateNote($input: UpdateNoteInput!) {
- updateNote(input: $input) {
- note {
- ...DesignNote
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/mutations/upload_design.mutation.graphql b/app/assets/javascripts/design_management_legacy/graphql/mutations/upload_design.mutation.graphql
deleted file mode 100644
index d694e6558a0..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/mutations/upload_design.mutation.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "../fragments/design.fragment.graphql"
-
-mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
- designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) {
- designs {
- ...DesignItem
- versions {
- edges {
- node {
- id
- sha
- }
- }
- }
- }
- skippedDesigns {
- filename
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/queries/active_discussion.query.graphql b/app/assets/javascripts/design_management_legacy/graphql/queries/active_discussion.query.graphql
deleted file mode 100644
index 111023cea68..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/queries/active_discussion.query.graphql
+++ /dev/null
@@ -1,6 +0,0 @@
-query activeDiscussion {
- activeDiscussion @client {
- id
- source
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/queries/app_data.query.graphql b/app/assets/javascripts/design_management_legacy/graphql/queries/app_data.query.graphql
deleted file mode 100644
index e1269761206..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/queries/app_data.query.graphql
+++ /dev/null
@@ -1,4 +0,0 @@
-query projectFullPath {
- projectPath @client
- issueIid @client
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/queries/design_permissions.query.graphql b/app/assets/javascripts/design_management_legacy/graphql/queries/design_permissions.query.graphql
deleted file mode 100644
index a87b256dc95..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/queries/design_permissions.query.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-query permissions($fullPath: ID!, $iid: String!) {
- project(fullPath: $fullPath) {
- id
- issue(iid: $iid) {
- userPermissions {
- createDesign
- }
- }
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management_legacy/graphql/queries/get_design.query.graphql
deleted file mode 100644
index 07a9af55787..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/queries/get_design.query.graphql
+++ /dev/null
@@ -1,31 +0,0 @@
-#import "../fragments/design.fragment.graphql"
-#import "~/graphql_shared/fragments/author.fragment.graphql"
-
-query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [String!]) {
- project(fullPath: $fullPath) {
- id
- issue(iid: $iid) {
- designCollection {
- designs(atVersion: $atVersion, filenames: $filenames) {
- edges {
- node {
- ...DesignItem
- issue {
- title
- webPath
- webUrl
- participants {
- edges {
- node {
- ...Author
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/queries/get_design_list.query.graphql b/app/assets/javascripts/design_management_legacy/graphql/queries/get_design_list.query.graphql
deleted file mode 100644
index 121a50555b3..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/queries/get_design_list.query.graphql
+++ /dev/null
@@ -1,26 +0,0 @@
-#import "../fragments/design_list.fragment.graphql"
-#import "../fragments/version.fragment.graphql"
-
-query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
- project(fullPath: $fullPath) {
- id
- issue(iid: $iid) {
- designCollection {
- designs(atVersion: $atVersion) {
- edges {
- node {
- ...DesignListItem
- }
- }
- }
- versions {
- edges {
- node {
- ...VersionListItem
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/design_management_legacy/graphql/typedefs.graphql b/app/assets/javascripts/design_management_legacy/graphql/typedefs.graphql
deleted file mode 100644
index fdbad4a90e0..00000000000
--- a/app/assets/javascripts/design_management_legacy/graphql/typedefs.graphql
+++ /dev/null
@@ -1,12 +0,0 @@
-type ActiveDiscussion {
- id: ID
- source: String
-}
-
-extend type Query {
- activeDiscussion: ActiveDiscussion
-}
-
-extend type Mutation {
- updateActiveDiscussion(id: ID!, source: String!): Boolean
-}
diff --git a/app/assets/javascripts/design_management_legacy/index.js b/app/assets/javascripts/design_management_legacy/index.js
deleted file mode 100644
index 1fc5779515a..00000000000
--- a/app/assets/javascripts/design_management_legacy/index.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// This application is being moved, please do not touch this files
-// Please see https://gitlab.com/gitlab-org/gitlab/-/issues/14744#note_364468096 for details
-
-import $ from 'jquery';
-import Vue from 'vue';
-import createRouter from './router';
-import App from './components/app.vue';
-import apolloProvider from './graphql';
-import getDesignListQuery from './graphql/queries/get_design_list.query.graphql';
-import { DESIGNS_ROUTE_NAME, ROOT_ROUTE_NAME } from './router/constants';
-
-export default () => {
- const el = document.querySelector('.js-design-management');
- const badge = document.querySelector('.js-designs-count');
- const { issueIid, projectPath, issuePath } = el.dataset;
- const router = createRouter(issuePath);
-
- $('.js-issue-tabs').on('shown.bs.tab', ({ target: { id } }) => {
- if (id === 'designs' && router.currentRoute.name === ROOT_ROUTE_NAME) {
- router.push({ name: DESIGNS_ROUTE_NAME });
- } else if (id === 'discussion') {
- router.push({ name: ROOT_ROUTE_NAME });
- }
- });
-
- apolloProvider.clients.defaultClient.cache.writeData({
- data: {
- projectPath,
- issueIid,
- activeDiscussion: {
- __typename: 'ActiveDiscussion',
- id: null,
- source: null,
- },
- },
- });
-
- apolloProvider.clients.defaultClient
- .watchQuery({
- query: getDesignListQuery,
- variables: {
- fullPath: projectPath,
- iid: issueIid,
- atVersion: null,
- },
- })
- .subscribe(({ data }) => {
- if (badge) {
- badge.textContent = data.project.issue.designCollection.designs.edges.length;
- }
- });
-
- return new Vue({
- el,
- router,
- apolloProvider,
- render(createElement) {
- return createElement(App);
- },
- });
-};
diff --git a/app/assets/javascripts/design_management_legacy/mixins/all_designs.js b/app/assets/javascripts/design_management_legacy/mixins/all_designs.js
deleted file mode 100644
index 544429928d2..00000000000
--- a/app/assets/javascripts/design_management_legacy/mixins/all_designs.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { propertyOf } from 'lodash';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { s__ } from '~/locale';
-import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import { extractNodes } from '../utils/design_management_utils';
-import allVersionsMixin from './all_versions';
-import { DESIGNS_ROUTE_NAME } from '../router/constants';
-
-export default {
- mixins: [allVersionsMixin],
- apollo: {
- designs: {
- query: getDesignListQuery,
- variables() {
- return {
- fullPath: this.projectPath,
- iid: this.issueIid,
- atVersion: this.designsVersion,
- };
- },
- update: data => {
- const designEdges = propertyOf(data)(['project', 'issue', 'designCollection', 'designs']);
- if (designEdges) {
- return extractNodes(designEdges);
- }
- return [];
- },
- error() {
- this.error = true;
- },
- result() {
- if (this.$route.query.version && !this.hasValidVersion) {
- createFlash(
- s__(
- 'DesignManagement|Requested design version does not exist. Showing latest version instead',
- ),
- );
- this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } });
- }
- },
- },
- },
- data() {
- return {
- designs: [],
- error: false,
- };
- },
-};
diff --git a/app/assets/javascripts/design_management_legacy/mixins/all_versions.js b/app/assets/javascripts/design_management_legacy/mixins/all_versions.js
deleted file mode 100644
index 3966fe71732..00000000000
--- a/app/assets/javascripts/design_management_legacy/mixins/all_versions.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import appDataQuery from '../graphql/queries/app_data.query.graphql';
-import { findVersionId } from '../utils/design_management_utils';
-
-export default {
- apollo: {
- appData: {
- query: appDataQuery,
- manual: true,
- result({ data: { projectPath, issueIid } }) {
- this.projectPath = projectPath;
- this.issueIid = issueIid;
- },
- },
- allVersions: {
- query: getDesignListQuery,
- variables() {
- return {
- fullPath: this.projectPath,
- iid: this.issueIid,
- atVersion: null,
- };
- },
- update: data => data.project.issue.designCollection.versions.edges,
- },
- },
- computed: {
- hasValidVersion() {
- return (
- this.$route.query.version &&
- this.allVersions &&
- this.allVersions.some(version => version.node.id.endsWith(this.$route.query.version))
- );
- },
- designsVersion() {
- return this.hasValidVersion
- ? `gid://gitlab/DesignManagement::Version/${this.$route.query.version}`
- : null;
- },
- latestVersionId() {
- const latestVersion = this.allVersions[0];
- return latestVersion && findVersionId(latestVersion.node.id);
- },
- isLatestVersion() {
- if (this.allVersions.length > 0) {
- return (
- !this.$route.query.version ||
- !this.latestVersionId ||
- this.$route.query.version === this.latestVersionId
- );
- }
- return true;
- },
- },
- data() {
- return {
- allVersions: [],
- projectPath: '',
- issueIid: null,
- };
- },
-};
diff --git a/app/assets/javascripts/design_management_legacy/pages/design/index.vue b/app/assets/javascripts/design_management_legacy/pages/design/index.vue
deleted file mode 100644
index 2ada9eff8c6..00000000000
--- a/app/assets/javascripts/design_management_legacy/pages/design/index.vue
+++ /dev/null
@@ -1,378 +0,0 @@
-<script>
-import Mousetrap from 'mousetrap';
-import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
-import { ApolloMutation } from 'vue-apollo';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { fetchPolicies } from '~/lib/graphql';
-import allVersionsMixin from '../../mixins/all_versions';
-import Toolbar from '../../components/toolbar/index.vue';
-import DesignDestroyer from '../../components/design_destroyer.vue';
-import DesignScaler from '../../components/design_scaler.vue';
-import DesignPresentation from '../../components/design_presentation.vue';
-import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
-import DesignSidebar from '../../components/design_sidebar.vue';
-import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
-import appDataQuery from '../../graphql/queries/app_data.query.graphql';
-import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
-import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
-import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
-import {
- extractDiscussions,
- extractDesign,
- updateImageDiffNoteOptimisticResponse,
-} from '../../utils/design_management_utils';
-import {
- updateStoreAfterAddImageDiffNote,
- updateStoreAfterUpdateImageDiffNote,
-} from '../../utils/cache_update';
-import {
- ADD_DISCUSSION_COMMENT_ERROR,
- ADD_IMAGE_DIFF_NOTE_ERROR,
- UPDATE_IMAGE_DIFF_NOTE_ERROR,
- DESIGN_NOT_FOUND_ERROR,
- DESIGN_VERSION_NOT_EXIST_ERROR,
- UPDATE_NOTE_ERROR,
- designDeletionError,
-} from '../../utils/error_messages';
-import { trackDesignDetailView } from '../../utils/tracking';
-import { DESIGNS_ROUTE_NAME } from '../../router/constants';
-import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
-
-export default {
- components: {
- ApolloMutation,
- DesignReplyForm,
- DesignPresentation,
- DesignScaler,
- DesignDestroyer,
- Toolbar,
- GlLoadingIcon,
- GlAlert,
- DesignSidebar,
- },
- mixins: [allVersionsMixin],
- props: {
- id: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- design: {},
- comment: '',
- annotationCoordinates: null,
- projectPath: '',
- errorMessage: '',
- issueIid: '',
- scale: 1,
- resolvedDiscussionsExpanded: false,
- };
- },
- apollo: {
- appData: {
- query: appDataQuery,
- manual: true,
- result({ data: { projectPath, issueIid } }) {
- this.projectPath = projectPath;
- this.issueIid = issueIid;
- },
- },
- design: {
- query: getDesignQuery,
- // We want to see cached design version if we have one, and fetch newer version on the background to update discussions
- fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
- variables() {
- return this.designVariables;
- },
- update: data => extractDesign(data),
- result(res) {
- this.onDesignQueryResult(res);
- },
- error() {
- this.onQueryError(DESIGN_NOT_FOUND_ERROR);
- },
- },
- },
- computed: {
- isFirstLoading() {
- // We only want to show spinner on initial design load (when opened from a deep link to design)
- // If we already have cached a design, loading shouldn't be indicated to user
- return this.$apollo.queries.design.loading && !this.design.filename;
- },
- discussions() {
- if (!this.design.discussions) {
- return [];
- }
- return extractDiscussions(this.design.discussions);
- },
- markdownPreviewPath() {
- return `/${this.projectPath}/preview_markdown?target_type=Issue`;
- },
- isSubmitButtonDisabled() {
- return this.comment.trim().length === 0;
- },
- designVariables() {
- return {
- fullPath: this.projectPath,
- iid: this.issueIid,
- filenames: [this.$route.params.id],
- atVersion: this.designsVersion,
- };
- },
- mutationPayload() {
- const { x, y, width, height } = this.annotationCoordinates;
- return {
- noteableId: this.design.id,
- body: this.comment,
- position: {
- headSha: this.design.diffRefs.headSha,
- baseSha: this.design.diffRefs.baseSha,
- startSha: this.design.diffRefs.startSha,
- x,
- y,
- width,
- height,
- paths: {
- newPath: this.design.fullPath,
- },
- },
- };
- },
- isAnnotating() {
- return Boolean(this.annotationCoordinates);
- },
- resolvedDiscussions() {
- return this.discussions.filter(discussion => discussion.resolved);
- },
- },
- watch: {
- resolvedDiscussions(val) {
- if (!val.length) {
- this.resolvedDiscussionsExpanded = false;
- }
- },
- },
- mounted() {
- Mousetrap.bind('esc', this.closeDesign);
- this.trackEvent();
- // We need to reset the active discussion when opening a new design
- this.updateActiveDiscussion();
- },
- beforeDestroy() {
- Mousetrap.unbind('esc', this.closeDesign);
- },
- methods: {
- addImageDiffNoteToStore(
- store,
- {
- data: { createImageDiffNote },
- },
- ) {
- updateStoreAfterAddImageDiffNote(
- store,
- createImageDiffNote,
- getDesignQuery,
- this.designVariables,
- );
- },
- updateImageDiffNoteInStore(
- store,
- {
- data: { updateImageDiffNote },
- },
- ) {
- return updateStoreAfterUpdateImageDiffNote(
- store,
- updateImageDiffNote,
- getDesignQuery,
- this.designVariables,
- );
- },
- onMoveNote({ noteId, discussionId, position }) {
- const discussion = this.discussions.find(({ id }) => id === discussionId);
- const note = discussion.notes.find(
- ({ discussion: noteDiscussion }) => noteDiscussion.id === discussionId,
- );
-
- const mutationPayload = {
- optimisticResponse: updateImageDiffNoteOptimisticResponse(note, {
- position,
- }),
- variables: {
- input: {
- id: noteId,
- position,
- },
- },
- mutation: updateImageDiffNoteMutation,
- update: this.updateImageDiffNoteInStore,
- };
-
- return this.$apollo.mutate(mutationPayload).catch(e => this.onUpdateImageDiffNoteError(e));
- },
- onDesignQueryResult({ data, loading }) {
- // On the initial load with cache-and-network policy data is undefined while loading is true
- // To prevent throwing an error, we don't perform any logic until loading is false
- if (loading) {
- return;
- }
-
- if (!data || !extractDesign(data)) {
- this.onQueryError(DESIGN_NOT_FOUND_ERROR);
- } else if (this.$route.query.version && !this.hasValidVersion) {
- this.onQueryError(DESIGN_VERSION_NOT_EXIST_ERROR);
- }
- },
- onQueryError(message) {
- // because we redirect user to /designs (the issue page),
- // we want to create these flashes on the issue page
- createFlash(message);
- this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME });
- },
- onError(message, e) {
- this.errorMessage = message;
- throw e;
- },
- onCreateImageDiffNoteError(e) {
- this.onError(ADD_IMAGE_DIFF_NOTE_ERROR, e);
- },
- onUpdateNoteError(e) {
- this.onError(UPDATE_NOTE_ERROR, e);
- },
- onDesignDiscussionError(e) {
- this.onError(ADD_DISCUSSION_COMMENT_ERROR, e);
- },
- onUpdateImageDiffNoteError(e) {
- this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e);
- },
- onDesignDeleteError(e) {
- this.onError(designDeletionError({ singular: true }), e);
- },
- onResolveDiscussionError(e) {
- this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e);
- },
- openCommentForm(annotationCoordinates) {
- this.annotationCoordinates = annotationCoordinates;
- if (this.$refs.newDiscussionForm) {
- this.$refs.newDiscussionForm.focusInput();
- }
- },
- closeCommentForm() {
- this.comment = '';
- this.annotationCoordinates = null;
- },
- closeDesign() {
- this.$router.push({
- name: this.$options.DESIGNS_ROUTE_NAME,
- query: this.$route.query,
- });
- },
- trackEvent() {
- // TODO: This needs to be made aware of referers, or if it's rendered in a different context than a Issue
- trackDesignDetailView(
- 'issue-design-collection',
- 'issue',
- this.$route.query.version || this.latestVersionId,
- this.isLatestVersion,
- );
- },
- updateActiveDiscussion(id) {
- this.$apollo.mutate({
- mutation: updateActiveDiscussionMutation,
- variables: {
- id,
- source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion,
- },
- });
- },
- toggleResolvedComments() {
- this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded;
- },
- },
- createImageDiffNoteMutation,
- DESIGNS_ROUTE_NAME,
-};
-</script>
-
-<template>
- <div
- class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
- >
- <gl-loading-icon v-if="isFirstLoading" size="xl" class="align-self-center" />
- <template v-else>
- <div class="d-flex overflow-hidden flex-grow-1 flex-column position-relative">
- <design-destroyer
- :filenames="[design.filename]"
- :project-path="projectPath"
- :iid="issueIid"
- @done="$router.push({ name: $options.DESIGNS_ROUTE_NAME })"
- @error="onDesignDeleteError"
- >
- <template #default="{ mutate, loading }">
- <toolbar
- :id="id"
- :is-deleting="loading"
- :is-latest-version="isLatestVersion"
- v-bind="design"
- @delete="mutate"
- />
- </template>
- </design-destroyer>
-
- <div v-if="errorMessage" class="p-3">
- <gl-alert variant="danger" @dismiss="errorMessage = null">
- {{ errorMessage }}
- </gl-alert>
- </div>
- <design-presentation
- :image="design.image"
- :image-name="design.filename"
- :discussions="discussions"
- :is-annotating="isAnnotating"
- :scale="scale"
- :resolved-discussions-expanded="resolvedDiscussionsExpanded"
- @openCommentForm="openCommentForm"
- @closeCommentForm="closeCommentForm"
- @moveNote="onMoveNote"
- />
-
- <div class="design-scaler-wrapper position-absolute mb-4 d-flex-center">
- <design-scaler @scale="scale = $event" />
- </div>
- </div>
- <design-sidebar
- :design="design"
- :resolved-discussions-expanded="resolvedDiscussionsExpanded"
- :markdown-preview-path="markdownPreviewPath"
- @onDesignDiscussionError="onDesignDiscussionError"
- @onCreateImageDiffNoteError="onCreateImageDiffNoteError"
- @updateNoteError="onUpdateNoteError"
- @resolveDiscussionError="onResolveDiscussionError"
- @toggleResolvedComments="toggleResolvedComments"
- >
- <template #replyForm>
- <apollo-mutation
- v-if="isAnnotating"
- #default="{ mutate, loading }"
- :mutation="$options.createImageDiffNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- :update="addImageDiffNoteToStore"
- @done="closeCommentForm"
- @error="onCreateImageDiffNoteError"
- >
- <design-reply-form
- ref="newDiscussionForm"
- v-model="comment"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- @submitForm="mutate"
- @cancelForm="closeCommentForm"
- /> </apollo-mutation
- ></template>
- </design-sidebar>
- </template>
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/pages/index.vue b/app/assets/javascripts/design_management_legacy/pages/index.vue
deleted file mode 100644
index 81532d75b7d..00000000000
--- a/app/assets/javascripts/design_management_legacy/pages/index.vue
+++ /dev/null
@@ -1,323 +0,0 @@
-<script>
-import { GlLoadingIcon, GlDeprecatedButton, GlAlert } from '@gitlab/ui';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { s__, sprintf } from '~/locale';
-import UploadButton from '../components/upload/button.vue';
-import DeleteButton from '../components/delete_button.vue';
-import Design from '../components/list/item.vue';
-import DesignDestroyer from '../components/design_destroyer.vue';
-import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue';
-import DesignDropzone from '../components/upload/design_dropzone.vue';
-import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql';
-import permissionsQuery from '../graphql/queries/design_permissions.query.graphql';
-import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import allDesignsMixin from '../mixins/all_designs';
-import {
- UPLOAD_DESIGN_ERROR,
- EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
- EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
- designUploadSkippedWarning,
- designDeletionError,
-} from '../utils/error_messages';
-import { updateStoreAfterUploadDesign } from '../utils/cache_update';
-import {
- designUploadOptimisticResponse,
- isValidDesignFile,
-} from '../utils/design_management_utils';
-import { getFilename } from '~/lib/utils/file_upload';
-import { DESIGNS_ROUTE_NAME } from '../router/constants';
-
-const MAXIMUM_FILE_UPLOAD_LIMIT = 10;
-
-export default {
- components: {
- GlLoadingIcon,
- GlAlert,
- GlDeprecatedButton,
- UploadButton,
- Design,
- DesignDestroyer,
- DesignVersionDropdown,
- DeleteButton,
- DesignDropzone,
- },
- mixins: [allDesignsMixin],
- apollo: {
- permissions: {
- query: permissionsQuery,
- variables() {
- return {
- fullPath: this.projectPath,
- iid: this.issueIid,
- };
- },
- update: data => data.project.issue.userPermissions,
- },
- },
- data() {
- return {
- permissions: {
- createDesign: false,
- },
- filesToBeSaved: [],
- selectedDesigns: [],
- };
- },
- computed: {
- isLoading() {
- return this.$apollo.queries.designs.loading || this.$apollo.queries.permissions.loading;
- },
- isSaving() {
- return this.filesToBeSaved.length > 0;
- },
- canCreateDesign() {
- return this.permissions.createDesign;
- },
- showToolbar() {
- return this.canCreateDesign && this.allVersions.length > 0;
- },
- hasDesigns() {
- return this.designs.length > 0;
- },
- hasSelectedDesigns() {
- return this.selectedDesigns.length > 0;
- },
- canDeleteDesigns() {
- return this.isLatestVersion && this.hasSelectedDesigns;
- },
- projectQueryBody() {
- return {
- query: getDesignListQuery,
- variables: { fullPath: this.projectPath, iid: this.issueIid, atVersion: null },
- };
- },
- selectAllButtonText() {
- return this.hasSelectedDesigns
- ? s__('DesignManagement|Deselect all')
- : s__('DesignManagement|Select all');
- },
- },
- mounted() {
- this.toggleOnPasteListener(this.$route.name);
- },
- methods: {
- resetFilesToBeSaved() {
- this.filesToBeSaved = [];
- },
- /**
- * Determine if a design upload is valid, given [files]
- * @param {Array<File>} files
- */
- isValidDesignUpload(files) {
- if (!this.canCreateDesign) return false;
-
- if (files.length > MAXIMUM_FILE_UPLOAD_LIMIT) {
- createFlash(
- sprintf(
- s__(
- 'DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again.',
- ),
- {
- upload_limit: MAXIMUM_FILE_UPLOAD_LIMIT,
- },
- ),
- );
-
- return false;
- }
- return true;
- },
- onUploadDesign(files) {
- // convert to Array so that we have Array methods (.map, .some, etc.)
- this.filesToBeSaved = Array.from(files);
- if (!this.isValidDesignUpload(this.filesToBeSaved)) return null;
-
- const mutationPayload = {
- optimisticResponse: designUploadOptimisticResponse(this.filesToBeSaved),
- variables: {
- files: this.filesToBeSaved,
- projectPath: this.projectPath,
- iid: this.issueIid,
- },
- context: {
- hasUpload: true,
- },
- mutation: uploadDesignMutation,
- update: this.afterUploadDesign,
- };
-
- return this.$apollo
- .mutate(mutationPayload)
- .then(res => this.onUploadDesignDone(res))
- .catch(() => this.onUploadDesignError());
- },
- afterUploadDesign(
- store,
- {
- data: { designManagementUpload },
- },
- ) {
- updateStoreAfterUploadDesign(store, designManagementUpload, this.projectQueryBody);
- },
- onUploadDesignDone(res) {
- const skippedFiles = res?.data?.designManagementUpload?.skippedDesigns || [];
- const skippedWarningMessage = designUploadSkippedWarning(this.filesToBeSaved, skippedFiles);
- if (skippedWarningMessage) {
- createFlash(skippedWarningMessage, 'warning');
- }
-
- // if this upload resulted in a new version being created, redirect user to the latest version
- if (!this.isLatestVersion) {
- this.$router.push({ name: DESIGNS_ROUTE_NAME }, () => {});
- }
- this.resetFilesToBeSaved();
- },
- onUploadDesignError() {
- this.resetFilesToBeSaved();
- createFlash(UPLOAD_DESIGN_ERROR);
- },
- changeSelectedDesigns(filename) {
- if (this.isDesignSelected(filename)) {
- this.selectedDesigns = this.selectedDesigns.filter(design => design !== filename);
- } else {
- this.selectedDesigns.push(filename);
- }
- },
- toggleDesignsSelection() {
- if (this.hasSelectedDesigns) {
- this.selectedDesigns = [];
- } else {
- this.selectedDesigns = this.designs.map(design => design.filename);
- }
- },
- isDesignSelected(filename) {
- return this.selectedDesigns.includes(filename);
- },
- isDesignToBeSaved(filename) {
- return this.filesToBeSaved.some(file => file.name === filename);
- },
- canSelectDesign(filename) {
- return this.isLatestVersion && this.canCreateDesign && !this.isDesignToBeSaved(filename);
- },
- onDesignDelete() {
- this.selectedDesigns = [];
- if (this.$route.query.version) this.$router.push({ name: DESIGNS_ROUTE_NAME });
- },
- onDesignDeleteError() {
- const errorMessage = designDeletionError({ singular: this.selectedDesigns.length === 1 });
- createFlash(errorMessage);
- },
- onExistingDesignDropzoneChange(files, existingDesignFilename) {
- const filesArr = Array.from(files);
-
- if (filesArr.length > 1) {
- createFlash(EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE);
- return;
- }
-
- if (!filesArr.some(({ name }) => existingDesignFilename === name)) {
- createFlash(EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE);
- return;
- }
-
- this.onUploadDesign(files);
- },
- onDesignPaste(event) {
- const { clipboardData } = event;
- const files = Array.from(clipboardData.files);
- if (clipboardData && files.length > 0) {
- if (!files.some(isValidDesignFile)) {
- return;
- }
- event.preventDefault();
- let filename = getFilename(event);
- if (!filename || filename === 'image.png') {
- filename = `design_${Date.now()}.png`;
- }
- const newFile = new File([files[0]], filename);
- this.onUploadDesign([newFile]);
- }
- },
- toggleOnPasteListener(route) {
- if (route === DESIGNS_ROUTE_NAME) {
- document.addEventListener('paste', this.onDesignPaste);
- } else {
- document.removeEventListener('paste', this.onDesignPaste);
- }
- },
- },
- beforeRouteUpdate(to, from, next) {
- this.toggleOnPasteListener(to.name);
- this.selectedDesigns = [];
- next();
- },
- beforeRouteLeave(to, from, next) {
- this.toggleOnPasteListener(to.name);
- next();
- },
-};
-</script>
-
-<template>
- <div>
- <header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex">
- <div class="d-flex justify-content-between align-items-center w-100">
- <design-version-dropdown />
- <div :class="['qa-selector-toolbar', { 'd-flex': hasDesigns, 'd-none': !hasDesigns }]">
- <gl-deprecated-button
- v-if="isLatestVersion"
- variant="link"
- class="mr-2 js-select-all"
- @click="toggleDesignsSelection"
- >{{ selectAllButtonText }}</gl-deprecated-button
- >
- <design-destroyer
- #default="{ mutate, loading }"
- :filenames="selectedDesigns"
- :project-path="projectPath"
- :iid="issueIid"
- @done="onDesignDelete"
- @error="onDesignDeleteError"
- >
- <delete-button
- v-if="isLatestVersion"
- :is-deleting="loading"
- button-class="btn-danger btn-inverted mr-2"
- :has-selected-designs="hasSelectedDesigns"
- @deleteSelectedDesigns="mutate()"
- >
- {{ s__('DesignManagement|Delete selected') }}
- <gl-loading-icon v-if="loading" inline class="ml-1" />
- </delete-button>
- </design-destroyer>
- <upload-button v-if="canCreateDesign" :is-saving="isSaving" @upload="onUploadDesign" />
- </div>
- </div>
- </header>
- <div class="mt-4">
- <gl-loading-icon v-if="isLoading" size="md" />
- <gl-alert v-else-if="error" variant="danger" :dismissible="false">
- {{ __('An error occurred while loading designs. Please try again.') }}
- </gl-alert>
- <ol v-else class="list-unstyled row">
- <li class="col-md-6 col-lg-4 mb-3">
- <design-dropzone class="design-list-item" @change="onUploadDesign" />
- </li>
- <li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-4 mb-3">
- <design-dropzone @change="onExistingDesignDropzoneChange($event, design.filename)"
- ><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)"
- /></design-dropzone>
-
- <input
- v-if="canSelectDesign(design.filename)"
- :checked="isDesignSelected(design.filename)"
- type="checkbox"
- class="design-checkbox"
- @change="changeSelectedDesigns(design.filename)"
- />
- </li>
- </ol>
- </div>
- <router-view :key="$route.fullPath" />
- </div>
-</template>
diff --git a/app/assets/javascripts/design_management_legacy/router/constants.js b/app/assets/javascripts/design_management_legacy/router/constants.js
deleted file mode 100644
index abeef520e33..00000000000
--- a/app/assets/javascripts/design_management_legacy/router/constants.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const ROOT_ROUTE_NAME = 'root';
-export const DESIGNS_ROUTE_NAME = 'designs';
-export const DESIGN_ROUTE_NAME = 'design';
diff --git a/app/assets/javascripts/design_management_legacy/router/index.js b/app/assets/javascripts/design_management_legacy/router/index.js
deleted file mode 100644
index 28a81ed0278..00000000000
--- a/app/assets/javascripts/design_management_legacy/router/index.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import $ from 'jquery';
-import Vue from 'vue';
-import VueRouter from 'vue-router';
-import routes from './routes';
-import { DESIGN_ROUTE_NAME } from './constants';
-import { getPageLayoutElement } from '~/design_management_legacy/utils/design_management_utils';
-import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '../constants';
-
-Vue.use(VueRouter);
-
-export default function createRouter(base) {
- const router = new VueRouter({
- base,
- mode: 'history',
- routes,
- });
- const pageEl = getPageLayoutElement();
-
- router.beforeEach(({ meta: { el }, name }, _, next) => {
- $(`#${el}`).tab('show');
-
- // apply a fullscreen layout style in Design View (a.k.a design detail)
- if (pageEl) {
- if (name === DESIGN_ROUTE_NAME) {
- pageEl.classList.add(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
- } else {
- pageEl.classList.remove(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
- }
- }
-
- next();
- });
-
- return router;
-}
diff --git a/app/assets/javascripts/design_management_legacy/router/routes.js b/app/assets/javascripts/design_management_legacy/router/routes.js
deleted file mode 100644
index 788910e5514..00000000000
--- a/app/assets/javascripts/design_management_legacy/router/routes.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import Home from '../pages/index.vue';
-import DesignDetail from '../pages/design/index.vue';
-import { ROOT_ROUTE_NAME, DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants';
-
-export default [
- {
- name: ROOT_ROUTE_NAME,
- path: '/',
- component: Home,
- meta: {
- el: 'discussion',
- },
- },
- {
- name: DESIGNS_ROUTE_NAME,
- path: '/designs',
- component: Home,
- meta: {
- el: 'designs',
- },
- children: [
- {
- name: DESIGN_ROUTE_NAME,
- path: ':id',
- component: DesignDetail,
- meta: {
- el: 'designs',
- },
- beforeEnter(
- {
- params: { id },
- },
- from,
- next,
- ) {
- if (typeof id === 'string') {
- next();
- }
- },
- props: ({ params: { id } }) => ({ id }),
- },
- ],
- },
-];
diff --git a/app/assets/javascripts/design_management_legacy/utils/cache_update.js b/app/assets/javascripts/design_management_legacy/utils/cache_update.js
deleted file mode 100644
index 5ba6f84c413..00000000000
--- a/app/assets/javascripts/design_management_legacy/utils/cache_update.js
+++ /dev/null
@@ -1,276 +0,0 @@
-/* eslint-disable @gitlab/require-i18n-strings */
-
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { extractCurrentDiscussion, extractDesign } from './design_management_utils';
-import {
- ADD_IMAGE_DIFF_NOTE_ERROR,
- UPDATE_IMAGE_DIFF_NOTE_ERROR,
- ADD_DISCUSSION_COMMENT_ERROR,
- designDeletionError,
-} from './error_messages';
-
-const deleteDesignsFromStore = (store, query, selectedDesigns) => {
- const data = store.readQuery(query);
-
- const changedDesigns = data.project.issue.designCollection.designs.edges.filter(
- ({ node }) => !selectedDesigns.includes(node.filename),
- );
- data.project.issue.designCollection.designs.edges = [...changedDesigns];
-
- store.writeQuery({
- ...query,
- data,
- });
-};
-
-/**
- * Adds a new version of designs to store
- *
- * @param {Object} store
- * @param {Object} query
- * @param {Object} version
- */
-const addNewVersionToStore = (store, query, version) => {
- if (!version) return;
-
- const data = store.readQuery(query);
- const newEdge = { node: version, __typename: 'DesignVersionEdge' };
-
- data.project.issue.designCollection.versions.edges = [
- newEdge,
- ...data.project.issue.designCollection.versions.edges,
- ];
-
- store.writeQuery({
- ...query,
- data,
- });
-};
-
-const addDiscussionCommentToStore = (store, createNote, query, queryVariables, discussionId) => {
- const data = store.readQuery({
- query,
- variables: queryVariables,
- });
-
- const design = extractDesign(data);
- const currentDiscussion = extractCurrentDiscussion(design.discussions, discussionId);
- currentDiscussion.notes.nodes = [...currentDiscussion.notes.nodes, createNote.note];
-
- design.notesCount += 1;
- if (
- !design.issue.participants.edges.some(
- participant => participant.node.username === createNote.note.author.username,
- )
- ) {
- design.issue.participants.edges = [
- ...design.issue.participants.edges,
- {
- __typename: 'UserEdge',
- node: {
- __typename: 'User',
- ...createNote.note.author,
- },
- },
- ];
- }
- store.writeQuery({
- query,
- variables: queryVariables,
- data: {
- ...data,
- design: {
- ...design,
- },
- },
- });
-};
-
-const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) => {
- const data = store.readQuery({
- query,
- variables,
- });
- const newDiscussion = {
- __typename: 'Discussion',
- id: createImageDiffNote.note.discussion.id,
- replyId: createImageDiffNote.note.discussion.replyId,
- resolvable: true,
- resolved: false,
- resolvedAt: null,
- resolvedBy: null,
- notes: {
- __typename: 'NoteConnection',
- nodes: [createImageDiffNote.note],
- },
- };
- const design = extractDesign(data);
- const notesCount = design.notesCount + 1;
- design.discussions.nodes = [...design.discussions.nodes, newDiscussion];
- if (
- !design.issue.participants.edges.some(
- participant => participant.node.username === createImageDiffNote.note.author.username,
- )
- ) {
- design.issue.participants.edges = [
- ...design.issue.participants.edges,
- {
- __typename: 'UserEdge',
- node: {
- __typename: 'User',
- ...createImageDiffNote.note.author,
- },
- },
- ];
- }
- store.writeQuery({
- query,
- variables,
- data: {
- ...data,
- design: {
- ...design,
- notesCount,
- },
- },
- });
-};
-
-const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables) => {
- const data = store.readQuery({
- query,
- variables,
- });
-
- const design = extractDesign(data);
- const discussion = extractCurrentDiscussion(
- design.discussions,
- updateImageDiffNote.note.discussion.id,
- );
-
- discussion.notes = {
- ...discussion.notes,
- nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)],
- };
-
- store.writeQuery({
- query,
- variables,
- data: {
- ...data,
- design,
- },
- });
-};
-
-const addNewDesignToStore = (store, designManagementUpload, query) => {
- const data = store.readQuery(query);
-
- const newDesigns = data.project.issue.designCollection.designs.edges.reduce((acc, design) => {
- if (!acc.find(d => d.filename === design.node.filename)) {
- acc.push(design.node);
- }
-
- return acc;
- }, designManagementUpload.designs);
-
- let newVersionNode;
- const findNewVersions = designManagementUpload.designs.find(design => design.versions);
-
- if (findNewVersions) {
- const findNewVersionsEdges = findNewVersions.versions.edges;
-
- if (findNewVersionsEdges && findNewVersionsEdges.length) {
- newVersionNode = [findNewVersionsEdges[0]];
- }
- }
-
- const newVersions = [
- ...(newVersionNode || []),
- ...data.project.issue.designCollection.versions.edges,
- ];
-
- const updatedDesigns = {
- __typename: 'DesignCollection',
- designs: {
- __typename: 'DesignConnection',
- edges: newDesigns.map(design => ({
- __typename: 'DesignEdge',
- node: design,
- })),
- },
- versions: {
- __typename: 'DesignVersionConnection',
- edges: newVersions,
- },
- };
-
- data.project.issue.designCollection = updatedDesigns;
-
- store.writeQuery({
- ...query,
- data,
- });
-};
-
-const onError = (data, message) => {
- createFlash(message);
- throw new Error(data.errors);
-};
-
-export const hasErrors = ({ errors = [] }) => errors?.length;
-
-/**
- * Updates a store after design deletion
- *
- * @param {Object} store
- * @param {Object} data
- * @param {Object} query
- * @param {Array} designs
- */
-export const updateStoreAfterDesignsDelete = (store, data, query, designs) => {
- if (hasErrors(data)) {
- onError(data, designDeletionError({ singular: designs.length === 1 }));
- } else {
- deleteDesignsFromStore(store, query, designs);
- addNewVersionToStore(store, query, data.version);
- }
-};
-
-export const updateStoreAfterAddDiscussionComment = (
- store,
- data,
- query,
- queryVariables,
- discussionId,
-) => {
- if (hasErrors(data)) {
- onError(data, ADD_DISCUSSION_COMMENT_ERROR);
- } else {
- addDiscussionCommentToStore(store, data, query, queryVariables, discussionId);
- }
-};
-
-export const updateStoreAfterAddImageDiffNote = (store, data, query, queryVariables) => {
- if (hasErrors(data)) {
- onError(data, ADD_IMAGE_DIFF_NOTE_ERROR);
- } else {
- addImageDiffNoteToStore(store, data, query, queryVariables);
- }
-};
-
-export const updateStoreAfterUpdateImageDiffNote = (store, data, query, queryVariables) => {
- if (hasErrors(data)) {
- onError(data, UPDATE_IMAGE_DIFF_NOTE_ERROR);
- } else {
- updateImageDiffNoteInStore(store, data, query, queryVariables);
- }
-};
-
-export const updateStoreAfterUploadDesign = (store, data, query) => {
- if (hasErrors(data)) {
- onError(data, data.errors[0]);
- } else {
- addNewDesignToStore(store, data, query);
- }
-};
diff --git a/app/assets/javascripts/design_management_legacy/utils/design_management_utils.js b/app/assets/javascripts/design_management_legacy/utils/design_management_utils.js
deleted file mode 100644
index 22705cf67a1..00000000000
--- a/app/assets/javascripts/design_management_legacy/utils/design_management_utils.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import { uniqueId } from 'lodash';
-import { VALID_DESIGN_FILE_MIMETYPE } from '../constants';
-
-export const isValidDesignFile = ({ type }) =>
- (type.match(VALID_DESIGN_FILE_MIMETYPE.regex) || []).length > 0;
-
-/**
- * Returns formatted array that doesn't contain
- * `edges`->`node` nesting
- *
- * @param {Array} elements
- */
-
-export const extractNodes = elements => elements.edges.map(({ node }) => node);
-
-/**
- * Returns formatted array of discussions that doesn't contain
- * `edges`->`node` nesting for child notes
- *
- * @param {Array} discussions
- */
-
-export const extractDiscussions = discussions =>
- discussions.nodes.map((discussion, index) => ({
- ...discussion,
- index: index + 1,
- notes: discussion.notes.nodes,
- }));
-
-/**
- * Returns a discussion with the given id from discussions array
- *
- * @param {Array} discussions
- */
-
-export const extractCurrentDiscussion = (discussions, id) =>
- discussions.nodes.find(discussion => discussion.id === id);
-
-export const findVersionId = id => (id.match('::Version/(.+$)') || [])[1];
-
-export const findNoteId = id => (id.match('DiffNote/(.+$)') || [])[1];
-
-export const extractDesigns = data => data.project.issue.designCollection.designs.edges;
-
-export const extractDesign = data => (extractDesigns(data) || [])[0]?.node;
-
-/**
- * Generates optimistic response for a design upload mutation
- * @param {Array<File>} files
- */
-export const designUploadOptimisticResponse = files => {
- const designs = files.map(file => ({
- // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
- // eslint-disable-next-line @gitlab/require-i18n-strings
- __typename: 'Design',
- id: -uniqueId(),
- image: '',
- imageV432x230: '',
- filename: file.name,
- fullPath: '',
- notesCount: 0,
- event: 'NONE',
- diffRefs: {
- __typename: 'DiffRefs',
- baseSha: '',
- startSha: '',
- headSha: '',
- },
- discussions: {
- __typename: 'DesignDiscussion',
- nodes: [],
- },
- versions: {
- __typename: 'DesignVersionConnection',
- edges: {
- __typename: 'DesignVersionEdge',
- node: {
- __typename: 'DesignVersion',
- id: -uniqueId(),
- sha: -uniqueId(),
- },
- },
- },
- }));
-
- return {
- // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
- // eslint-disable-next-line @gitlab/require-i18n-strings
- __typename: 'Mutation',
- designManagementUpload: {
- __typename: 'DesignManagementUploadPayload',
- designs,
- skippedDesigns: [],
- errors: [],
- },
- };
-};
-
-/**
- * Generates optimistic response for a design upload mutation
- * @param {Array<File>} files
- */
-export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
- // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
- // eslint-disable-next-line @gitlab/require-i18n-strings
- __typename: 'Mutation',
- updateImageDiffNote: {
- __typename: 'UpdateImageDiffNotePayload',
- note: {
- ...note,
- position: {
- ...note.position,
- ...position,
- },
- },
- errors: [],
- },
-});
-
-const normalizeAuthor = author => ({
- ...author,
- web_url: author.webUrl,
- avatar_url: author.avatarUrl,
-});
-
-export const extractParticipants = users => users.edges.map(({ node }) => normalizeAuthor(node));
-
-export const getPageLayoutElement = () => document.querySelector('.layout-page');
diff --git a/app/assets/javascripts/design_management_legacy/utils/error_messages.js b/app/assets/javascripts/design_management_legacy/utils/error_messages.js
deleted file mode 100644
index 7666c726c2f..00000000000
--- a/app/assets/javascripts/design_management_legacy/utils/error_messages.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { __, s__, n__, sprintf } from '~/locale';
-
-export const ADD_DISCUSSION_COMMENT_ERROR = s__(
- 'DesignManagement|Could not add a new comment. Please try again.',
-);
-
-export const ADD_IMAGE_DIFF_NOTE_ERROR = s__(
- 'DesignManagement|Could not create new discussion. Please try again.',
-);
-
-export const UPDATE_IMAGE_DIFF_NOTE_ERROR = s__(
- 'DesignManagement|Could not update discussion. Please try again.',
-);
-
-export const UPDATE_NOTE_ERROR = s__('DesignManagement|Could not update note. Please try again.');
-
-export const UPLOAD_DESIGN_ERROR = s__(
- 'DesignManagement|Error uploading a new design. Please try again.',
-);
-
-export const UPLOAD_DESIGN_INVALID_FILETYPE_ERROR = __(
- 'Could not upload your designs as one or more files uploaded are not supported.',
-);
-
-export const DESIGN_NOT_FOUND_ERROR = __('Could not find design.');
-
-export const DESIGN_VERSION_NOT_EXIST_ERROR = __('Requested design version does not exist.');
-
-const DESIGN_UPLOAD_SKIPPED_MESSAGE = s__('DesignManagement|Upload skipped.');
-
-const ALL_DESIGNS_SKIPPED_MESSAGE = `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${s__(
- 'The designs you tried uploading did not change.',
-)}`;
-
-export const EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE = __(
- 'You can only upload one design when dropping onto an existing design.',
-);
-
-export const EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE = __(
- 'You must upload a file with the same file name when dropping onto an existing design.',
-);
-
-const MAX_SKIPPED_FILES_LISTINGS = 5;
-
-const oneDesignSkippedMessage = filename =>
- `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${sprintf(s__('DesignManagement|%{filename} did not change.'), {
- filename,
- })}`;
-
-/**
- * Return warning message indicating that some (but not all) uploaded
- * files were skipped.
- * @param {Array<{ filename }>} skippedFiles
- */
-const someDesignsSkippedMessage = skippedFiles => {
- const designsSkippedMessage = `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${s__(
- 'Some of the designs you tried uploading did not change:',
- )}`;
-
- const moreText = sprintf(s__(`DesignManagement|and %{moreCount} more.`), {
- moreCount: skippedFiles.length - MAX_SKIPPED_FILES_LISTINGS,
- });
-
- return `${designsSkippedMessage} ${skippedFiles
- .slice(0, MAX_SKIPPED_FILES_LISTINGS)
- .map(({ filename }) => filename)
- .join(', ')}${skippedFiles.length > MAX_SKIPPED_FILES_LISTINGS ? `, ${moreText}` : '.'}`;
-};
-
-export const designDeletionError = ({ singular = true } = {}) => {
- const design = singular ? __('a design') : __('designs');
- return sprintf(s__('Could not delete %{design}. Please try again.'), {
- design,
- });
-};
-
-/**
- * Return warning message, if applicable, that one, some or all uploaded
- * files were skipped.
- * @param {Array<{ filename }>} uploadedDesigns
- * @param {Array<{ filename }>} skippedFiles
- */
-export const designUploadSkippedWarning = (uploadedDesigns, skippedFiles) => {
- if (skippedFiles.length === 0) {
- return null;
- }
-
- if (skippedFiles.length === uploadedDesigns.length) {
- const { filename } = skippedFiles[0];
-
- return n__(oneDesignSkippedMessage(filename), ALL_DESIGNS_SKIPPED_MESSAGE, skippedFiles.length);
- }
-
- return someDesignsSkippedMessage(skippedFiles);
-};
diff --git a/app/assets/javascripts/design_management_legacy/utils/tracking.js b/app/assets/javascripts/design_management_legacy/utils/tracking.js
deleted file mode 100644
index 49fa306914c..00000000000
--- a/app/assets/javascripts/design_management_legacy/utils/tracking.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import Tracking from '~/tracking';
-
-// Tracking Constants
-const DESIGN_TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/design_management_context/jsonschema/1-0-0';
-const DESIGN_TRACKING_PAGE_NAME = 'projects:issues:design';
-const DESIGN_TRACKING_EVENT_NAME = 'view_design';
-
-export function trackDesignDetailView(
- referer = '',
- owner = '',
- designVersion = 1,
- latestVersion = false,
-) {
- Tracking.event(DESIGN_TRACKING_PAGE_NAME, DESIGN_TRACKING_EVENT_NAME, {
- label: DESIGN_TRACKING_EVENT_NAME,
- context: {
- schema: DESIGN_TRACKING_CONTEXT_SCHEMA,
- data: {
- 'design-version-number': designVersion,
- 'design-is-current-version': latestVersion,
- 'internal-object-referrer': referer,
- 'design-collection-owner': owner,
- },
- },
- });
-}
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index 1eb89b41495..ed68ca5cae9 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapGetters } from 'vuex';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue';
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 36e8951bea3..776d8459515 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -1,6 +1,6 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import FileTree from '~/vue_shared/components/file_tree.vue';
import IdeFileRow from './ide_file_row.vue';
import NavDropdown from './nav_dropdown.vue';
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index 46852e4ddd9..3be592baf29 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -208,6 +208,31 @@ export default {
isEmpty() {
return !this.incidents.list?.length;
},
+ showList() {
+ return !this.isEmpty || this.errored || this.loading;
+ },
+ activeClosedTabHasNoIncidents() {
+ const { all, closed } = this.incidentsCount || {};
+ const isClosedTabActive = this.statusFilter === this.$options.statusTabs[1].filters;
+
+ return isClosedTabActive && all && !closed;
+ },
+ emptyStateData() {
+ const {
+ emptyState: { title, emptyClosedTabTitle, description },
+ createIncidentBtnLabel,
+ } = this.$options.i18n;
+
+ if (this.activeClosedTabHasNoIncidents) {
+ return { title: emptyClosedTabTitle };
+ }
+ return {
+ title,
+ description,
+ btnLink: this.newIncidentPath,
+ btnText: createIncidentBtnLabel,
+ };
+ },
},
methods: {
onInputChange: debounce(function debounceSearch(input) {
@@ -279,7 +304,7 @@ export default {
</gl-tabs>
<gl-button
- v-if="!isEmpty"
+ v-if="!isEmpty || activeClosedTabHasNoIncidents"
class="gl-my-3 gl-mr-5 create-incident-button"
data-testid="createIncidentBtn"
data-qa-selector="create_incident_button"
@@ -307,6 +332,7 @@ export default {
{{ s__('IncidentManagement|Incidents') }}
</h4>
<gl-table
+ v-if="showList"
:items="incidents.list || []"
:fields="availableFields"
:show-empty="true"
@@ -379,21 +405,20 @@ export default {
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>
- <template #empty>
- <gl-empty-state
- v-if="!errored"
- :title="$options.i18n.emptyState.title"
- :svg-path="emptyListSvgPath"
- :description="$options.i18n.emptyState.description"
- :primary-button-link="newIncidentPath"
- :primary-button-text="$options.i18n.createIncidentBtnLabel"
- />
- <span v-else>
- {{ $options.i18n.noIncidents }}
- </span>
+ <template v-if="errored" #empty>
+ {{ $options.i18n.noIncidents }}
</template>
</gl-table>
+ <gl-empty-state
+ v-else
+ :title="emptyStateData.title"
+ :svg-path="emptyListSvgPath"
+ :description="emptyStateData.description"
+ :primary-button-link="emptyStateData.btnLink"
+ :primary-button-text="emptyStateData.btnText"
+ />
+
<gl-pagination
v-if="showPaginationControls"
:value="pagination.currentPage"
diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js
index 42016ed49dd..289b36d9848 100644
--- a/app/assets/javascripts/incidents/constants.js
+++ b/app/assets/javascripts/incidents/constants.js
@@ -9,6 +9,7 @@ export const I18N = {
searchPlaceholder: __('Search results…'),
emptyState: {
title: s__('IncidentManagement|Display your incidents in a dedicated view'),
+ emptyClosedTabTitle: s__('IncidentManagement|There are no closed incidents'),
description: s__(
'IncidentManagement|All alerts promoted to incidents will automatically be displayed within the list. You can also create a new incident using the button below.',
),
diff --git a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
index fecb7353efb..0d4f5bce965 100644
--- a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
+++ b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
@@ -3,7 +3,7 @@ import { toNumber, omit } from 'lodash';
import {
GlEmptyState,
GlPagination,
- GlSkeletonLoading,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import { deprecatedCreateFlash as flash } from '~/flash';
diff --git a/app/assets/javascripts/mr_popover/components/mr_popover.vue b/app/assets/javascripts/mr_popover/components/mr_popover.vue
index e6bf7a6ec02..bf810978648 100644
--- a/app/assets/javascripts/mr_popover/components/mr_popover.vue
+++ b/app/assets/javascripts/mr_popover/components/mr_popover.vue
@@ -1,6 +1,6 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
-import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
+import { GlPopover, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import query from '../queries/merge_request.query.graphql';
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 3940b4b4724..4372970927f 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -17,7 +17,7 @@ import Autosize from 'autosize';
import 'jquery.caret'; // required by at.js
import '@gitlab/at.js';
import Vue from 'vue';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import AjaxCache from '~/lib/utils/ajax_cache';
import syntaxHighlight from '~/syntax_highlight';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 2a3b3156cc0..c01cd8f8037 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,7 +1,7 @@
<script>
/* eslint-disable vue/no-v-html */
import { mapState, mapActions } from 'vuex';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index a577d2e1ecd..f38c860c5d0 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -25,12 +25,6 @@ export default function() {
initSentryErrorStackTraceApp();
initRelatedMergeRequestsApp();
- // This will be removed when we remove the `design_management_moved` feature flag
- // See https://gitlab.com/gitlab-org/gitlab/-/issues/223197
- import(/* webpackChunkName: 'design_management' */ '~/design_management_legacy')
- .then(module => module.default())
- .catch(() => {});
-
import(/* webpackChunkName: 'design_management' */ '~/design_management')
.then(module => module.default())
.catch(() => {});
diff --git a/app/assets/javascripts/releases/components/app_index.vue b/app/assets/javascripts/releases/components/app_index.vue
index 67085ecca2b..22485a3344b 100644
--- a/app/assets/javascripts/releases/components/app_index.vue
+++ b/app/assets/javascripts/releases/components/app_index.vue
@@ -1,6 +1,11 @@
<script>
import { mapState, mapActions } from 'vuex';
-import { GlSkeletonLoading, GlEmptyState, GlLink, GlButton } from '@gitlab/ui';
+import {
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
+ GlEmptyState,
+ GlLink,
+ GlButton,
+} from '@gitlab/ui';
import {
getParameterByName,
historyPushState,
diff --git a/app/assets/javascripts/releases/components/app_show.vue b/app/assets/javascripts/releases/components/app_show.vue
index 0e65d722952..8b89f0cf3fc 100644
--- a/app/assets/javascripts/releases/components/app_show.vue
+++ b/app/assets/javascripts/releases/components/app_show.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapActions } from 'vuex';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import ReleaseBlock from './release_block.vue';
export default {
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index fd70a6419fc..c6652c57c1f 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -1,5 +1,5 @@
<script>
-import { GlSkeletonLoading, GlButton } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
import projectPathQuery from '../../queries/project_path.query.graphql';
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index c6c41e4146a..d749a8c0dee 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -4,7 +4,7 @@ import { escapeRegExp } from 'lodash';
import {
GlBadge,
GlLink,
- GlSkeletonLoading,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlTooltipDirective,
GlLoadingIcon,
GlIcon,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue
index c7d9453a5c9..4de41dd5887 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue
@@ -1,5 +1,5 @@
<script>
-import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import MrWidgetExpanableSection from '../mr_widget_expandable_section.vue';
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index 0248dc79441..6bb05e59f6b 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -3,7 +3,7 @@
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { forEach, escape } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
diff --git a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
index e75ac8c54bc..53dbae39608 100644
--- a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
@@ -1,5 +1,5 @@
<script>
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
export default {
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 4fa9724448d..b0f2f846ab2 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -19,7 +19,12 @@
*/
import $ from 'jquery';
import { mapGetters, mapActions, mapState } from 'vuex';
-import { GlDeprecatedButton, GlSkeletonLoading, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import {
+ GlDeprecatedButton,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
+ GlTooltipDirective,
+ GlIcon,
+} from '@gitlab/ui';
import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history';
import noteHeader from '~/notes/components/note_header.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index fd4fcca312a..6aaff000845 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -1,6 +1,6 @@
<script>
/* eslint-disable vue/no-v-html */
-import { GlPopover, GlSkeletonLoading, GlIcon } from '@gitlab/ui';
+import { GlPopover, GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlIcon } from '@gitlab/ui';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
diff --git a/app/views/projects/issues/_design_management.html.haml b/app/views/projects/issues/_design_management.html.haml
index 9d88d77eac9..0c962cdd2b6 100644
--- a/app/views/projects/issues/_design_management.html.haml
+++ b/app/views/projects/issues/_design_management.html.haml
@@ -4,20 +4,7 @@
- enable_lfs_message = s_("DesignManagement|To upload designs, you'll need to enable LFS. %{requirements_link_start}More information%{requirements_link_end}").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end }
- if @project.design_management_enabled?
- - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
- .js-design-management-new{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
- - else
- .js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
+ .js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
- else
- - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
- .gl-border-solid.gl-border-1.gl-border-gray-100.gl-rounded-base.gl-mt-5.gl-p-3.gl-text-center
- = enable_lfs_message
- - else
- .mt-4
- .row.empty-state
- .col-12
- .text-content
- %h4.center
- = _('The one place for your designs')
- %p.center
- = enable_lfs_message
+ .gl-border-solid.gl-border-1.gl-border-gray-100.gl-rounded-base.gl-mt-5.gl-p-3.gl-text-center
+ = enable_lfs_message
diff --git a/app/views/projects/issues/_tabs.html.haml b/app/views/projects/issues/_tabs.html.haml
deleted file mode 100644
index d998a01623f..00000000000
--- a/app/views/projects/issues/_tabs.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%ul.nav-tabs.nav.nav-links{ role: 'tablist' }
- %li
- = link_to '#discussion-tab', class: 'active js-issue-tabs', id: 'discussion', role: 'tab', 'aria-controls': 'js-discussion', 'aria-selected': 'true', data: { toggle: 'tab', target: '#discussion-tab', qa_selector: 'discussion_tab_link' } do
- = _('Discussion')
- %span.badge.badge-pill.js-discussions-count
- %li
- = link_to '#designs-tab', class: 'js-issue-tabs', id: 'designs', role: 'tab', 'aria-controls': 'js-designs', 'aria-selected': 'false', data: { toggle: 'tab', target: '#designs-tab', qa_selector: 'designs_tab_link' } do
- = _('Designs')
- %span.badge.badge-pill.js-designs-count
-.tab-content
- #discussion-tab.tab-pane.show.active{ role: 'tabpanel', 'aria-labelledby': 'discussion', data: { qa_selector: 'discussion_tab_content' } }
- = render 'projects/issues/discussion'
- #designs-tab.tab-pane{ role: 'tabpanel', 'aria-labelledby': 'designs', data: { qa_selector: 'designs_tab_content' } }
- = render 'projects/issues/design_management'
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 8d8fdcf70ea..c762b044c3e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -76,8 +76,7 @@
- if @issue.sentry_issue.present?
#js-sentry-error-stack-trace{ data: error_details_data(@project, @issue.sentry_issue.sentry_issue_identifier) }
- - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
- = render 'projects/issues/design_management'
+ = render 'projects/issues/design_management'
= render_if_exists 'projects/issues/related_issues'
@@ -97,9 +96,6 @@
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' if show_new_branch_button?
- - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
- = render 'projects/issues/discussion'
- - else
- = render 'projects/issues/tabs'
+ = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
diff --git a/changelogs/unreleased/229636-incident-empty-state-polish.yml b/changelogs/unreleased/229636-incident-empty-state-polish.yml
new file mode 100644
index 00000000000..425c3d22ad9
--- /dev/null
+++ b/changelogs/unreleased/229636-incident-empty-state-polish.yml
@@ -0,0 +1,5 @@
+---
+title: Update empty state behavior for incidents list
+merge_request: 40872
+author:
+type: other
diff --git a/changelogs/unreleased/agent_gitops_sync_count.yml b/changelogs/unreleased/agent_gitops_sync_count.yml
new file mode 100644
index 00000000000..e1fb7bfe0f9
--- /dev/null
+++ b/changelogs/unreleased/agent_gitops_sync_count.yml
@@ -0,0 +1,5 @@
+---
+title: Add kubernetes_agent_gitops_sync usage ping metric
+merge_request: 40568
+author:
+type: other
diff --git a/changelogs/unreleased/dblessing-doorkeeper-hex-generator.yml b/changelogs/unreleased/dblessing-doorkeeper-hex-generator.yml
new file mode 100644
index 00000000000..7b21903d555
--- /dev/null
+++ b/changelogs/unreleased/dblessing-doorkeeper-hex-generator.yml
@@ -0,0 +1,5 @@
+---
+title: Restore doorkeeper generator to hex due to breaking change
+merge_request: 41169
+author:
+type: fixed
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 2f98471772f..6b54b5074d5 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -3,6 +3,10 @@ Doorkeeper.configure do
# Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
orm :active_record
+ # Restore to pre-5.1 generator due to breaking change.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/244371
+ default_generator_method :hex
+
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
diff --git a/doc/administration/auth/ldap/ldap-troubleshooting.md b/doc/administration/auth/ldap/ldap-troubleshooting.md
index 75183f54990..e823dc61023 100644
--- a/doc/administration/auth/ldap/ldap-troubleshooting.md
+++ b/doc/administration/auth/ldap/ldap-troubleshooting.md
@@ -136,6 +136,27 @@ are true for the user in question:
- Run [an LDAP check command](#ldap-check) to make sure that the LDAP settings
are correct and [GitLab can see your users](#no-users-are-found).
+#### Access denied for your LDAP account
+
+There is [a bug](https://gitlab.com/gitlab-org/gitlab/-/issues/235930) that
+may affect users with [Auditor level access](../../auditor_users.md). When
+downgrading from Premium/Ultimate, Auditor users who try to sign in
+may see the following message: `Access denied for your LDAP account`.
+
+We have a workaround, based on toggling the access level of affected users:
+
+1. As an administrator, go to **Admin Area > Overview > Users**.
+1. Select the name of the affected user.
+1. In the user's administrative page, press **Edit** on the top right of the page.
+1. Change the user's access level from **Regular** to **Admin** (or vice versa),
+ and press **Save changes** at the bottom of the page.
+1. Press **Edit** on the top right of the user's profile page
+ again.
+1. Restore the user's original access level (**Regular** or **Admin**)
+ and press **Save changes** again.
+
+The user should now be able to sign in.
+
#### Email has already been taken
A user tries to sign-in with the correct LDAP credentials, is denied access,
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index 0cdc07b1f46..6863763ff24 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -169,9 +169,9 @@ Parameters:
| Attribute | Type | Required | Description |
|:------------|:--------|:---------|:-------------------------------------------------------------------|
-| `id` | integer | yes | ID of snippet to retrieve |
-| `ref` | string | yes | Reference to a tag, branch or commit |
-| `file_path` | string | yes | URL-encoded path to the file |
+| `id` | integer | yes | ID of snippet to retrieve. |
+| `ref` | string | yes | Reference to a tag, branch or commit. |
+| `file_path` | string | yes | URL-encoded path to the file. |
Example request:
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index f516f4080fa..bb7420e24e9 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -377,7 +377,7 @@ Alternatively, when users need to [link SAML to their existing GitLab.com accoun
| Cause | Solution |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| As mentioned in the [NameID](#nameid) section, if the NameID changes for any user, the user can be locked out. This is a common problem when an email address is used as the identifier. | Follow the steps outlined in the ["SAML authentication failed: User has already been taken"](#message-saml-authentication-failed-user-has-already-been-taken) section. If many users are affected, we recommend that you use the appropriate API. |
+| As mentioned in the [NameID](#nameid) section, if the NameID changes for any user, the user can be locked out. This is a common problem when an email address is used as the identifier. | Follow the steps outlined in the ["SAML authentication failed: User has already been taken"](#message-saml-authentication-failed-user-has-already-been-taken) section. |
### I need to change my SAML app
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 5e456c7986c..94136ae171d 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -72,39 +72,12 @@ and connect to GitLab through a personal access token. The details are explained
## The Design Management section
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223193) in GitLab 13.2, Designs are displayed directly on the issue description rather than on a separate tab.
-> - The new display is deployed behind a feature flag, enabled by default.
-> - It's enabled on GitLab.com.
-> - It cannot be enabled or disabled per-project.
-> - It's recommended for production use.
-> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-displaying-designs-on-the-issue-description-core-only). If disabled, it will move Designs back to the **Designs** tab.
+> - New display's feature flag [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/223197) in GitLab 13.4.
You can find to the **Design Management** section in the issue description:
![Designs section](img/design_management_v13_2.png)
-### Enable or disable displaying Designs on the issue description **(CORE ONLY)**
-
-Displaying Designs on the issue description is under development but ready for
-production use. It is deployed behind a feature flag that is **enabled by
-default**.
-[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
-can opt to disable it for your instance.
-
-To disable it:
-
-```ruby
-Feature.disable(:design_management_moved)
-```
-
-To enable it:
-
-```ruby
-Feature.enable(:design_management_moved)
-```
-
-By disabling this feature, designs will be displayed on the **Designs** tab
-instead of directly on the issue description.
-
## Adding designs
To upload Design images, drag files from your computer and drop them in the Design Management section,
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index 49ace13f322..bab829a609e 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -96,6 +96,25 @@ module API
gitaly_repository: gitaly_repository(project)
}
end
+
+ desc 'POST usage metrics' do
+ detail 'Updates usage metrics for agent'
+ end
+ route_setting :authentication, cluster_agent_token_allowed: true
+ params do
+ requires :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
+ end
+ post '/usage_metrics' do
+ gitops_sync_count = params[:gitops_sync_count]
+
+ if gitops_sync_count < 0
+ bad_request!('gitops_sync_count must be greater than or equal to zero')
+ else
+ Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_gitops_sync(gitops_sync_count)
+
+ no_content!
+ end
+ end
end
end
end
diff --git a/lib/gitlab/error_tracking.rb b/lib/gitlab/error_tracking.rb
index 803acef9a40..8d5611411c9 100644
--- a/lib/gitlab/error_tracking.rb
+++ b/lib/gitlab/error_tracking.rb
@@ -26,8 +26,6 @@ module Gitlab
# Sanitize fields based on those sanitized from Rails.
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
config.processors << ::Gitlab::ErrorTracking::Processor::SidekiqProcessor
- config.processors << ::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor
-
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
config.tags = extra_tags_from_env.merge(program: Gitlab.process_name)
diff --git a/lib/gitlab/error_tracking/processor/grpc_error_processor.rb b/lib/gitlab/error_tracking/processor/grpc_error_processor.rb
deleted file mode 100644
index a19e066a660..00000000000
--- a/lib/gitlab/error_tracking/processor/grpc_error_processor.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module ErrorTracking
- module Processor
- class GrpcErrorProcessor < ::Raven::Processor
- DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)')
-
- def process(value)
- return value unless grpc_exception?(value)
-
- process_message(value)
- process_exception_values(value)
- process_custom_fingerprint(value)
-
- value
- end
-
- def grpc_exception?(value)
- value[:exception] && value[:message].start_with?('GRPC::')
- end
-
- def process_message(value)
- message, debug_str = split_debug_error_string(value[:message])
-
- return unless message
-
- value[:message] = message
- extra = value[:extra] || {}
- extra[:grpc_debug_error_string] = debug_str if debug_str
- end
-
- def process_exception_values(value)
- exceptions = value.dig(:exception, :values)
-
- return unless exceptions.is_a?(Array)
-
- exceptions.each do |entry|
- message, _ = split_debug_error_string(entry[:value])
- entry[:value] = message if message
- end
- end
-
- def process_custom_fingerprint(value)
- fingerprint = value[:fingerprint]
-
- return value unless custom_grpc_fingerprint?(fingerprint)
-
- message, _ = split_debug_error_string(fingerprint[1])
- fingerprint[1] = message if message
- end
-
- private
-
- def custom_grpc_fingerprint?(fingerprint)
- fingerprint.is_a?(Array) && fingerprint.length == 2 && fingerprint[0].start_with?('GRPC::')
- end
-
- def split_debug_error_string(message)
- return unless message
-
- match = DEBUG_ERROR_STRING_REGEX.match(message)
-
- return unless match
-
- [match[1], match[2]]
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index b6bffb11344..96f3487fd6f 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -13,7 +13,6 @@ module Gitlab
TAG_REF_PREFIX = "refs/tags/"
BRANCH_REF_PREFIX = "refs/heads/"
- BaseError = Class.new(StandardError)
CommandError = Class.new(BaseError)
CommitError = Class.new(BaseError)
OSError = Class.new(BaseError)
diff --git a/lib/gitlab/git/base_error.rb b/lib/gitlab/git/base_error.rb
new file mode 100644
index 00000000000..a7eaa82b347
--- /dev/null
+++ b/lib/gitlab/git/base_error.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class BaseError < StandardError
+ DEBUG_ERROR_STRING_REGEX = /(.*?) debug_error_string:.*$/m.freeze
+
+ def initialize(msg = nil)
+ if msg
+ raw_message = msg.to_s
+ match = DEBUG_ERROR_STRING_REGEX.match(raw_message)
+ raw_message = match[1] if match
+
+ super(raw_message)
+ else
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index a9e2ba4d85c..b2c296cc3d5 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -245,7 +245,8 @@ module Gitlab
Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
Gitlab::UsageDataCounters::SourceCodeCounter,
Gitlab::UsageDataCounters::MergeRequestCounter,
- Gitlab::UsageDataCounters::DesignsCounter
+ Gitlab::UsageDataCounters::DesignsCounter,
+ Gitlab::UsageDataCounters::KubernetesAgentCounter
]
end
diff --git a/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb b/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb
new file mode 100644
index 00000000000..eae42bdc4a1
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ class KubernetesAgentCounter < BaseCounter
+ PREFIX = 'kubernetes_agent'
+ KNOWN_EVENTS = %w[gitops_sync].freeze
+
+ class << self
+ def increment_gitops_sync(incr)
+ raise ArgumentError, 'must be greater than or equal to zero' if incr < 0
+
+ # rather then hitting redis for this no-op, we return early
+ # note: redis returns the increment, so we mimic this here
+ return 0 if incr == 0
+
+ increment_by(redis_key(:gitops_sync), incr)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/redis_counter.rb b/lib/gitlab/usage_data_counters/redis_counter.rb
index 75d5a75e3a4..2406f771fd8 100644
--- a/lib/gitlab/usage_data_counters/redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/redis_counter.rb
@@ -9,6 +9,12 @@ module Gitlab
Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) }
end
+ def increment_by(redis_counter_key, incr)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled
+
+ Gitlab::Redis::SharedState.with { |redis| redis.incrby(redis_counter_key, incr) }
+ end
+
def total_count(redis_counter_key)
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_counter_key).to_i }
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 17aed3bf769..ed03c8b01a0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -515,9 +515,6 @@ msgstr ""
msgid "%{level_name} is not allowed since the fork source project has lower visibility."
msgstr ""
-msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
-msgstr ""
-
msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
msgstr ""
@@ -7039,9 +7036,6 @@ msgstr ""
msgid "Could not create wiki page"
msgstr ""
-msgid "Could not delete %{design}. Please try again."
-msgstr ""
-
msgid "Could not delete chat nickname %{chat_name}."
msgstr ""
@@ -8046,9 +8040,6 @@ msgstr ""
msgid "Deleted chat nickname: %{chat_name}!"
msgstr ""
-msgid "Deleted in this version"
-msgstr ""
-
msgid "Deleted projects"
msgstr ""
@@ -8487,9 +8478,6 @@ msgstr ""
msgid "DesignManagement|Are you sure you want to cancel creating this comment?"
msgstr ""
-msgid "DesignManagement|Are you sure you want to delete the selected designs?"
-msgstr ""
-
msgid "DesignManagement|Cancel changes"
msgstr ""
@@ -8520,15 +8508,6 @@ msgstr ""
msgid "DesignManagement|Could not update note. Please try again."
msgstr ""
-msgid "DesignManagement|Delete"
-msgstr ""
-
-msgid "DesignManagement|Delete designs confirmation"
-msgstr ""
-
-msgid "DesignManagement|Delete selected"
-msgstr ""
-
msgid "DesignManagement|Deselect all"
msgstr ""
@@ -8745,9 +8724,6 @@ msgstr ""
msgid "Discuss a specific suggestion or question."
msgstr ""
-msgid "Discussion"
-msgstr ""
-
msgid "Discussion to reply to cannot be found"
msgstr ""
@@ -13115,6 +13091,9 @@ msgstr ""
msgid "IncidentManagement|Published to status page"
msgstr ""
+msgid "IncidentManagement|There are no closed incidents"
+msgstr ""
+
msgid "IncidentManagement|There was an error displaying the incidents."
msgstr ""
@@ -22750,12 +22729,6 @@ msgstr ""
msgid "Showing %{pageSize} of %{total} issues"
msgstr ""
-msgid "Showing Latest Version"
-msgstr ""
-
-msgid "Showing Version #%{versionNumber}"
-msgstr ""
-
msgid "Showing all issues"
msgstr ""
@@ -24421,9 +24394,6 @@ msgstr ""
msgid "The %{link_start}true-up model%{link_end} allows having more users, and additional users will incur a retroactive charge on renewal."
msgstr ""
-msgid "The %{true_up_link_start}true-up model%{link_end} has a retroactive charge for these users at the next renewal. If you want to update your license sooner to prevent this, %{support_link_start}please contact our Support team%{link_end}."
-msgstr ""
-
msgid "The %{type} contains the following error:"
msgid_plural "The %{type} contains the following errors:"
msgstr[0] ""
@@ -24659,9 +24629,6 @@ msgstr ""
msgid "The number of times an upload record could not find its file"
msgstr ""
-msgid "The one place for your designs"
-msgstr ""
-
msgid "The parent epic is confidential and can only contain confidential epics and issues"
msgstr ""
@@ -28554,6 +28521,9 @@ msgstr ""
msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
msgstr ""
+msgid "You'll be charged for %{true_up_link_start}users over license%{link_end} on a quartely or annual basis, depending on the terms of your agreement."
+msgstr ""
+
msgid "You'll be signed out from your current account automatically."
msgstr ""
@@ -28746,6 +28716,9 @@ msgstr ""
msgid "Your instance has %{remaining_user_count} users remaining of the %{total_user_count} included in your subscription. You can add more users than the number included in your license, and we will include the overage in your next bill."
msgstr ""
+msgid "Your instance has exceeded your subscription's licensed user count."
+msgstr ""
+
msgid "Your instance is approaching its licensed user count"
msgstr ""
diff --git a/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb b/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
index aff8951d9de..908e30478b2 100644
--- a/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
@@ -8,57 +8,26 @@ RSpec.describe 'User paginates issue designs', :js do
let(:project) { create(:project_empty_repo, :public) }
let(:issue) { create(:issue, project: project) }
- context 'design_management_moved flag disabled' do
- before do
- stub_feature_flags(design_management_moved: false)
- enable_design_management
-
- create_list(:design, 2, :with_file, issue: issue)
- visit project_issue_path(project, issue)
- click_link 'Designs'
- wait_for_requests
- find('.js-design-list-item', match: :first).click
- end
-
- it 'paginates to next design' do
- expect(find('.js-previous-design')[:disabled]).to eq('true')
-
- page.within(find('.js-design-header')) do
- expect(page).to have_content('1 of 2')
- end
-
- find('.js-next-design').click
-
- expect(find('.js-previous-design')[:disabled]).not_to eq('true')
-
- page.within(find('.js-design-header')) do
- expect(page).to have_content('2 of 2')
- end
- end
+ before do
+ enable_design_management
+ create_list(:design, 2, :with_file, issue: issue)
+ visit project_issue_path(project, issue)
+ find('.js-design-list-item', match: :first).click
end
- context 'design_management_moved flag enabled' do
- before do
- enable_design_management
- create_list(:design, 2, :with_file, issue: issue)
- visit project_issue_path(project, issue)
- find('.js-design-list-item', match: :first).click
- end
+ it 'paginates to next design' do
+ expect(find('.js-previous-design')[:disabled]).to eq('true')
- it 'paginates to next design' do
- expect(find('.js-previous-design')[:disabled]).to eq('true')
-
- page.within(find('.js-design-header')) do
- expect(page).to have_content('1 of 2')
- end
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('1 of 2')
+ end
- find('.js-next-design').click
+ find('.js-next-design').click
- expect(find('.js-previous-design')[:disabled]).not_to eq('true')
+ expect(find('.js-previous-design')[:disabled]).not_to eq('true')
- page.within(find('.js-design-header')) do
- expect(page).to have_content('2 of 2')
- end
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('2 of 2')
end
end
end
diff --git a/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb b/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
index 4e45312eac3..cfd8a4540ee 100644
--- a/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
+++ b/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
@@ -8,32 +8,13 @@ RSpec.describe 'User design permissions', :js do
let(:project) { create(:project_empty_repo, :public) }
let(:issue) { create(:issue, project: project) }
- context 'design_management_moved flag disabled' do
- before do
- enable_design_management
- stub_feature_flags(design_management_moved: false)
+ before do
+ enable_design_management
- visit project_issue_path(project, issue)
-
- click_link 'Designs'
-
- wait_for_requests
- end
-
- it 'user does not have permissions to upload design' do
- expect(page).not_to have_field('design_file')
- end
+ visit project_issue_path(project, issue)
end
- context 'design_management_moved flag enabled' do
- before do
- enable_design_management
-
- visit project_issue_path(project, issue)
- end
-
- it 'user does not have permissions to upload design' do
- expect(page).not_to have_field('design_file')
- end
+ it 'user does not have permissions to upload design' do
+ expect(page).not_to have_field('design_file')
end
end
diff --git a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
index 29a27992a0d..8998cae621d 100644
--- a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
@@ -11,81 +11,34 @@ RSpec.describe 'User uploads new design', :js do
before do
sign_in(user)
+ enable_design_management(feature_enabled)
+ visit project_issue_path(project, issue)
end
- context 'design_management_moved flag disabled' do
- before do
- enable_design_management(feature_enabled)
- stub_feature_flags(design_management_moved: false)
- visit project_issue_path(project, issue)
+ context "when the feature is available" do
+ let(:feature_enabled) { true }
- click_link 'Designs'
+ it 'uploads designs' do
+ upload_design(logo_fixture, count: 1)
- wait_for_requests
- end
-
- context "when the feature is available" do
- let(:feature_enabled) { true }
-
- it 'uploads designs' do
- upload_design(logo_fixture, count: 1)
-
- expect(page).to have_selector('.js-design-list-item', count: 1)
-
- within first('#designs-tab .js-design-list-item') do
- expect(page).to have_content('dk.png')
- end
+ expect(page).to have_selector('.js-design-list-item', count: 1)
- upload_design(gif_fixture, count: 2)
-
- # Known bug in the legacy implementation: new designs are inserted
- # in the beginning on the frontend.
- expect(page).to have_selector('.js-design-list-item', count: 2)
- expect(page.all('.js-design-list-item').map(&:text)).to eq(['banana_sample.gif', 'dk.png'])
+ within first('[data-testid="designs-root"] .js-design-list-item') do
+ expect(page).to have_content('dk.png')
end
- end
- context 'when the feature is not available' do
- let(:feature_enabled) { false }
+ upload_design(gif_fixture, count: 2)
- it 'shows the message about requirements' do
- expect(page).to have_content("To upload designs, you'll need to enable LFS.")
- end
+ expect(page).to have_selector('.js-design-list-item', count: 2)
+ expect(page.all('.js-design-list-item').map(&:text)).to eq(['dk.png', 'banana_sample.gif'])
end
end
- context 'design_management_moved flag enabled' do
- before do
- enable_design_management(feature_enabled)
- stub_feature_flags(design_management_moved: true)
- visit project_issue_path(project, issue)
- end
-
- context "when the feature is available" do
- let(:feature_enabled) { true }
+ context 'when the feature is not available' do
+ let(:feature_enabled) { false }
- it 'uploads designs' do
- upload_design(logo_fixture, count: 1)
-
- expect(page).to have_selector('.js-design-list-item', count: 1)
-
- within first('[data-testid="designs-root"] .js-design-list-item') do
- expect(page).to have_content('dk.png')
- end
-
- upload_design(gif_fixture, count: 2)
-
- expect(page).to have_selector('.js-design-list-item', count: 2)
- expect(page.all('.js-design-list-item').map(&:text)).to eq(['dk.png', 'banana_sample.gif'])
- end
- end
-
- context 'when the feature is not available' do
- let(:feature_enabled) { false }
-
- it 'shows the message about requirements' do
- expect(page).to have_content("To upload designs, you'll need to enable LFS.")
- end
+ it 'shows the message about requirements' do
+ expect(page).to have_content("To upload designs, you'll need to enable LFS.")
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_design_spec.rb b/spec/features/projects/issues/design_management/user_views_design_spec.rb
index 49245218e81..b513a4fe3fa 100644
--- a/spec/features/projects/issues/design_management/user_views_design_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_design_spec.rb
@@ -9,42 +9,19 @@ RSpec.describe 'User views issue designs', :js do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue) }
- context 'design_management_moved flag disabled' do
- before do
- enable_design_management
- stub_feature_flags(design_management_moved: false)
+ before do
+ enable_design_management
- visit project_issue_path(project, issue)
-
- click_link 'Designs'
- end
-
- it 'opens design detail' do
- click_link design.filename
-
- page.within(find('.js-design-header')) do
- expect(page).to have_content(design.filename)
- end
-
- expect(page).to have_selector('.js-design-image')
- end
+ visit project_issue_path(project, issue)
end
- context 'design_management_moved flag enabled' do
- before do
- enable_design_management
+ it 'opens design detail' do
+ click_link design.filename
- visit project_issue_path(project, issue)
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content(design.filename)
end
- it 'opens design detail' do
- click_link design.filename
-
- page.within(find('.js-design-header')) do
- expect(page).to have_content(design.filename)
- end
-
- expect(page).to have_selector('.js-design-image')
- end
+ expect(page).to have_selector('.js-design-image')
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_designs_spec.rb b/spec/features/projects/issues/design_management/user_views_designs_spec.rb
index 772a9ffbe6f..46c772027ad 100644
--- a/spec/features/projects/issues/design_management/user_views_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_designs_spec.rb
@@ -9,78 +9,37 @@ RSpec.describe 'User views issue designs', :js do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue) }
- context 'design_management_moved flag disabled' do
- before do
- enable_design_management
- stub_feature_flags(design_management_moved: false)
- end
-
- context 'navigates from the issue view' do
- before do
- visit project_issue_path(project, issue)
- click_link 'Designs'
- wait_for_requests
- end
-
- it 'fetches list of designs' do
- expect(page).to have_selector('.js-design-list-item', count: 1)
- end
- end
-
- context 'navigates directly to the design collection view' do
- before do
- visit designs_project_issue_path(project, issue)
- end
+ before do
+ enable_design_management
+ end
- it 'expands the sidebar' do
- expect(page).to have_selector('.layout-page.right-sidebar-expanded')
- end
+ context 'navigates from the issue view' do
+ before do
+ visit project_issue_path(project, issue)
end
- context 'navigates directly to the individual design view' do
- before do
- visit designs_project_issue_path(project, issue, vueroute: design.filename)
- end
-
- it 'sees the design' do
- expect(page).to have_selector('.js-design-detail')
- end
+ it 'fetches list of designs' do
+ expect(page).to have_selector('.js-design-list-item', count: 1)
end
end
- context 'design_management_moved flag enabled' do
+ context 'navigates directly to the design collection view' do
before do
- enable_design_management
+ visit designs_project_issue_path(project, issue)
end
- context 'navigates from the issue view' do
- before do
- visit project_issue_path(project, issue)
- end
-
- it 'fetches list of designs' do
- expect(page).to have_selector('.js-design-list-item', count: 1)
- end
+ it 'expands the sidebar' do
+ expect(page).to have_selector('.layout-page.right-sidebar-expanded')
end
+ end
- context 'navigates directly to the design collection view' do
- before do
- visit designs_project_issue_path(project, issue)
- end
-
- it 'expands the sidebar' do
- expect(page).to have_selector('.layout-page.right-sidebar-expanded')
- end
+ context 'navigates directly to the individual design view' do
+ before do
+ visit designs_project_issue_path(project, issue, vueroute: design.filename)
end
- context 'navigates directly to the individual design view' do
- before do
- visit designs_project_issue_path(project, issue, vueroute: design.filename)
- end
-
- it 'sees the design' do
- expect(page).to have_selector('.js-design-detail')
- end
+ it 'sees the design' do
+ expect(page).to have_selector('.js-design-detail')
end
end
end
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index c6a5f66a627..628c35ae839 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -1,6 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
import { mount } from '@vue/test-utils';
-import { GlLoadingIcon, GlPagination, GlSkeletonLoading, GlTable } from '@gitlab/ui';
+import {
+ GlLoadingIcon,
+ GlPagination,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
+ GlTable,
+} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import axios from '~/lib/utils/axios_utils';
import Clusters from '~/clusters_list/components/clusters.vue';
diff --git a/spec/frontend/design_management_legacy/components/__snapshots__/design_note_pin_spec.js.snap b/spec/frontend/design_management_legacy/components/__snapshots__/design_note_pin_spec.js.snap
deleted file mode 100644
index 62a0f675cff..00000000000
--- a/spec/frontend/design_management_legacy/components/__snapshots__/design_note_pin_spec.js.snap
+++ /dev/null
@@ -1,42 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design note pin component should match the snapshot of note when repositioning 1`] = `
-<button
- aria-label="Comment form position"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 btn-transparent comment-indicator"
- style="left: 10px; top: 10px; cursor: move;"
- type="button"
->
- <gl-icon-stub
- name="image-comment-dark"
- size="24"
- />
-</button>
-`;
-
-exports[`Design note pin component should match the snapshot of note with index 1`] = `
-<button
- aria-label="Comment '1' position"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 js-image-badge badge badge-pill"
- style="left: 10px; top: 10px;"
- type="button"
->
-
- 1
-
-</button>
-`;
-
-exports[`Design note pin component should match the snapshot of note without index 1`] = `
-<button
- aria-label="Comment form position"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 btn-transparent comment-indicator"
- style="left: 10px; top: 10px;"
- type="button"
->
- <gl-icon-stub
- name="image-comment-dark"
- size="24"
- />
-</button>
-`;
diff --git a/spec/frontend/design_management_legacy/components/__snapshots__/design_presentation_spec.js.snap b/spec/frontend/design_management_legacy/components/__snapshots__/design_presentation_spec.js.snap
deleted file mode 100644
index 189962c5b2e..00000000000
--- a/spec/frontend/design_management_legacy/components/__snapshots__/design_presentation_spec.js.snap
+++ /dev/null
@@ -1,104 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management design presentation component currentCommentForm is equal to current annotation position when isAnnotating is true 1`] = `
-<div
- class="h-100 w-100 p-3 overflow-auto position-relative"
->
- <div
- class="h-100 w-100 d-flex align-items-center position-relative"
- >
- <design-image-stub
- image="test.jpg"
- name="test"
- scale="1"
- />
-
- <design-overlay-stub
- currentcommentform="[object Object]"
- dimensions="[object Object]"
- notes=""
- position="[object Object]"
- />
- </div>
-</div>
-`;
-
-exports[`Design management design presentation component currentCommentForm is null when isAnnotating is false 1`] = `
-<div
- class="h-100 w-100 p-3 overflow-auto position-relative"
->
- <div
- class="h-100 w-100 d-flex align-items-center position-relative"
- >
- <design-image-stub
- image="test.jpg"
- name="test"
- scale="1"
- />
-
- <design-overlay-stub
- dimensions="[object Object]"
- notes=""
- position="[object Object]"
- />
- </div>
-</div>
-`;
-
-exports[`Design management design presentation component currentCommentForm is null when isAnnotating is true but annotation position is falsey 1`] = `
-<div
- class="h-100 w-100 p-3 overflow-auto position-relative"
->
- <div
- class="h-100 w-100 d-flex align-items-center position-relative"
- >
- <design-image-stub
- image="test.jpg"
- name="test"
- scale="1"
- />
-
- <design-overlay-stub
- dimensions="[object Object]"
- notes=""
- position="[object Object]"
- />
- </div>
-</div>
-`;
-
-exports[`Design management design presentation component renders empty state when no image provided 1`] = `
-<div
- class="h-100 w-100 p-3 overflow-auto position-relative"
->
- <div
- class="h-100 w-100 d-flex align-items-center position-relative"
- >
- <!---->
-
- <!---->
- </div>
-</div>
-`;
-
-exports[`Design management design presentation component renders image and overlay when image provided 1`] = `
-<div
- class="h-100 w-100 p-3 overflow-auto position-relative"
->
- <div
- class="h-100 w-100 d-flex align-items-center position-relative"
- >
- <design-image-stub
- image="test.jpg"
- name="test"
- scale="1"
- />
-
- <design-overlay-stub
- dimensions="[object Object]"
- notes=""
- position="[object Object]"
- />
- </div>
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/components/__snapshots__/design_scaler_spec.js.snap b/spec/frontend/design_management_legacy/components/__snapshots__/design_scaler_spec.js.snap
deleted file mode 100644
index cb4575cbd11..00000000000
--- a/spec/frontend/design_management_legacy/components/__snapshots__/design_scaler_spec.js.snap
+++ /dev/null
@@ -1,115 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management design scaler component minus and reset buttons are disabled when scale === 1 1`] = `
-<div
- class="design-scaler btn-group"
- role="group"
->
- <button
- class="btn"
- disabled="disabled"
- >
- <span
- class="d-flex-center gl-icon s16"
- >
-
- –
-
- </span>
- </button>
-
- <button
- class="btn"
- disabled="disabled"
- >
- <gl-icon-stub
- name="redo"
- size="16"
- />
- </button>
-
- <button
- class="btn"
- >
- <gl-icon-stub
- name="plus"
- size="16"
- />
- </button>
-</div>
-`;
-
-exports[`Design management design scaler component minus and reset buttons are enabled when scale > 1 1`] = `
-<div
- class="design-scaler btn-group"
- role="group"
->
- <button
- class="btn"
- >
- <span
- class="d-flex-center gl-icon s16"
- >
-
- –
-
- </span>
- </button>
-
- <button
- class="btn"
- >
- <gl-icon-stub
- name="redo"
- size="16"
- />
- </button>
-
- <button
- class="btn"
- >
- <gl-icon-stub
- name="plus"
- size="16"
- />
- </button>
-</div>
-`;
-
-exports[`Design management design scaler component plus button is disabled when scale === 2 1`] = `
-<div
- class="design-scaler btn-group"
- role="group"
->
- <button
- class="btn"
- >
- <span
- class="d-flex-center gl-icon s16"
- >
-
- –
-
- </span>
- </button>
-
- <button
- class="btn"
- >
- <gl-icon-stub
- name="redo"
- size="16"
- />
- </button>
-
- <button
- class="btn"
- disabled="disabled"
- >
- <gl-icon-stub
- name="plus"
- size="16"
- />
- </button>
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/components/__snapshots__/image_spec.js.snap b/spec/frontend/design_management_legacy/components/__snapshots__/image_spec.js.snap
deleted file mode 100644
index acaa62b11eb..00000000000
--- a/spec/frontend/design_management_legacy/components/__snapshots__/image_spec.js.snap
+++ /dev/null
@@ -1,68 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management large image component renders image 1`] = `
-<div
- class="m-auto js-design-image"
->
- <!---->
-
- <img
- alt="test"
- class="mh-100 img-fluid"
- src="test.jpg"
- />
-</div>
-`;
-
-exports[`Design management large image component renders loading state 1`] = `
-<div
- class="m-auto js-design-image"
- isloading="true"
->
- <!---->
-
- <img
- alt=""
- class="mh-100 img-fluid"
- src=""
- />
-</div>
-`;
-
-exports[`Design management large image component renders media broken icon on error 1`] = `
-<gl-icon-stub
- class="text-secondary-100"
- name="media-broken"
- size="48"
-/>
-`;
-
-exports[`Design management large image component sets correct classes and styles if imageStyle is set 1`] = `
-<div
- class="m-auto js-design-image"
->
- <!---->
-
- <img
- alt="test"
- class="mh-100"
- src="test.jpg"
- style="width: 100px; height: 100px;"
- />
-</div>
-`;
-
-exports[`Design management large image component zoom sets image style when zoomed 1`] = `
-<div
- class="m-auto js-design-image"
->
- <!---->
-
- <img
- alt="test"
- class="mh-100"
- src="test.jpg"
- style="width: 200px; height: 200px;"
- />
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/components/delete_button_spec.js b/spec/frontend/design_management_legacy/components/delete_button_spec.js
deleted file mode 100644
index 73b4908d06a..00000000000
--- a/spec/frontend/design_management_legacy/components/delete_button_spec.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
-import BatchDeleteButton from '~/design_management_legacy/components/delete_button.vue';
-
-describe('Batch delete button component', () => {
- let wrapper;
-
- const findButton = () => wrapper.find(GlDeprecatedButton);
- const findModal = () => wrapper.find(GlModal);
-
- function createComponent(isDeleting = false) {
- wrapper = shallowMount(BatchDeleteButton, {
- propsData: {
- isDeleting,
- },
- directives: {
- GlModalDirective,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders non-disabled button by default', () => {
- createComponent();
-
- expect(findButton().exists()).toBe(true);
- expect(findButton().attributes('disabled')).toBeFalsy();
- });
-
- it('renders disabled button when design is deleting', () => {
- createComponent(true);
- expect(findButton().attributes('disabled')).toBeTruthy();
- });
-
- it('emits `deleteSelectedDesigns` event on modal ok click', () => {
- createComponent();
- findButton().vm.$emit('click');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findModal().vm.$emit('ok');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted().deleteSelectedDesigns).toBeTruthy();
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_note_pin_spec.js b/spec/frontend/design_management_legacy/components/design_note_pin_spec.js
deleted file mode 100644
index 3077928cf86..00000000000
--- a/spec/frontend/design_management_legacy/components/design_note_pin_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import DesignNotePin from '~/design_management_legacy/components/design_note_pin.vue';
-
-describe('Design note pin component', () => {
- let wrapper;
-
- function createComponent(propsData = {}) {
- wrapper = shallowMount(DesignNotePin, {
- propsData: {
- position: {
- left: '10px',
- top: '10px',
- },
- ...propsData,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('should match the snapshot of note without index', () => {
- createComponent();
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('should match the snapshot of note with index', () => {
- createComponent({ label: 1 });
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('should match the snapshot of note when repositioning', () => {
- createComponent({ repositioning: true });
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('pinStyle', () => {
- it('sets cursor to `move` when repositioning = true', () => {
- createComponent({ repositioning: true });
- expect(wrapper.vm.pinStyle.cursor).toBe('move');
- });
-
- it('does not set cursor when repositioning = false', () => {
- createComponent();
- expect(wrapper.vm.pinStyle.cursor).toBe(undefined);
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_note_spec.js.snap b/spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_note_spec.js.snap
deleted file mode 100644
index b55bacb6fc5..00000000000
--- a/spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_note_spec.js.snap
+++ /dev/null
@@ -1,67 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design note component should match the snapshot 1`] = `
-<timeline-entry-item-stub
- class="design-note note-form"
- id="note_123"
->
- <user-avatar-link-stub
- imgalt=""
- imgcssclasses=""
- imgsize="40"
- imgsrc=""
- linkhref=""
- tooltipplacement="top"
- tooltiptext=""
- username=""
- />
-
- <div
- class="d-flex justify-content-between"
- >
- <div>
- <a
- class="js-user-link"
- data-user-id="author-id"
- >
- <span
- class="note-header-author-name bold"
- >
-
- </span>
-
- <!---->
-
- <span
- class="note-headline-light"
- >
- @
- </span>
- </a>
-
- <span
- class="note-headline-light note-headline-meta"
- >
- <span
- class="system-note-message"
- />
-
- <!---->
- </span>
- </div>
-
- <div
- class="gl-display-flex"
- >
-
- <!---->
- </div>
- </div>
-
- <div
- class="note-text js-note-text md"
- data-qa-selector="note_content"
- />
-
-</timeline-entry-item-stub>
-`;
diff --git a/spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_reply_form_spec.js.snap b/spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_reply_form_spec.js.snap
deleted file mode 100644
index e01c79e3520..00000000000
--- a/spec/frontend/design_management_legacy/components/design_notes/__snapshots__/design_reply_form_spec.js.snap
+++ /dev/null
@@ -1,15 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design reply form component renders button text as "Comment" when creating a comment 1`] = `
-"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
- <!---->
- Comment
-</button>"
-`;
-
-exports[`Design reply form component renders button text as "Save comment" when creating a comment 1`] = `
-"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
- <!---->
- Save comment
-</button>"
-`;
diff --git a/spec/frontend/design_management_legacy/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management_legacy/components/design_notes/design_discussion_spec.js
deleted file mode 100644
index 2330cb31f4d..00000000000
--- a/spec/frontend/design_management_legacy/components/design_notes/design_discussion_spec.js
+++ /dev/null
@@ -1,318 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { GlLoadingIcon } from '@gitlab/ui';
-import notes from '../../mock_data/notes';
-import DesignDiscussion from '~/design_management_legacy/components/design_notes/design_discussion.vue';
-import DesignNote from '~/design_management_legacy/components/design_notes/design_note.vue';
-import DesignReplyForm from '~/design_management_legacy/components/design_notes/design_reply_form.vue';
-import createNoteMutation from '~/design_management_legacy/graphql/mutations/create_note.mutation.graphql';
-import toggleResolveDiscussionMutation from '~/design_management_legacy/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
-import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
-import ToggleRepliesWidget from '~/design_management_legacy/components/design_notes/toggle_replies_widget.vue';
-
-const discussion = {
- id: '0',
- resolved: false,
- resolvable: true,
- notes,
-};
-
-describe('Design discussions component', () => {
- let wrapper;
-
- const findDesignNotes = () => wrapper.findAll(DesignNote);
- const findReplyPlaceholder = () => wrapper.find(ReplyPlaceholder);
- const findReplyForm = () => wrapper.find(DesignReplyForm);
- const findRepliesWidget = () => wrapper.find(ToggleRepliesWidget);
- const findResolveButton = () => wrapper.find('[data-testid="resolve-button"]');
- const findResolveIcon = () => wrapper.find('[data-testid="resolve-icon"]');
- const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]');
- const findResolveLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]');
-
- const mutationVariables = {
- mutation: createNoteMutation,
- update: expect.anything(),
- variables: {
- input: {
- noteableId: 'noteable-id',
- body: 'test',
- discussionId: '0',
- },
- },
- };
- const mutate = jest.fn(() => Promise.resolve());
- const $apollo = {
- mutate,
- };
-
- function createComponent(props = {}, data = {}) {
- wrapper = mount(DesignDiscussion, {
- propsData: {
- resolvedDiscussionsExpanded: true,
- discussion,
- noteableId: 'noteable-id',
- designId: 'design-id',
- discussionIndex: 1,
- discussionWithOpenForm: '',
- ...props,
- },
- data() {
- return {
- ...data,
- };
- },
- mocks: {
- $apollo,
- $route: {
- hash: '#note_1',
- },
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when discussion is not resolvable', () => {
- beforeEach(() => {
- createComponent({
- discussion: {
- ...discussion,
- resolvable: false,
- },
- });
- });
-
- it('does not render an icon to resolve a thread', () => {
- expect(findResolveIcon().exists()).toBe(false);
- });
-
- it('does not render a checkbox in reply form', () => {
- findReplyPlaceholder().vm.$emit('onClick');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findResolveCheckbox().exists()).toBe(false);
- });
- });
- });
-
- describe('when discussion is unresolved', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders correct amount of discussion notes', () => {
- expect(findDesignNotes()).toHaveLength(2);
- expect(findDesignNotes().wrappers.every(w => w.isVisible())).toBe(true);
- });
-
- it('renders reply placeholder', () => {
- expect(findReplyPlaceholder().isVisible()).toBe(true);
- });
-
- it('does not render toggle replies widget', () => {
- expect(findRepliesWidget().exists()).toBe(false);
- });
-
- it('renders a correct icon to resolve a thread', () => {
- expect(findResolveIcon().props('name')).toBe('check-circle');
- });
-
- it('renders a checkbox with Resolve thread text in reply form', () => {
- findReplyPlaceholder().vm.$emit('onClick');
- wrapper.setProps({ discussionWithOpenForm: discussion.id });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findResolveCheckbox().text()).toBe('Resolve thread');
- });
- });
-
- it('does not render resolved message', () => {
- expect(findResolvedMessage().exists()).toBe(false);
- });
- });
-
- describe('when discussion is resolved', () => {
- beforeEach(() => {
- createComponent({
- discussion: {
- ...discussion,
- resolved: true,
- resolvedBy: notes[0].author,
- resolvedAt: '2020-05-08T07:10:45Z',
- },
- });
- });
-
- it('shows only the first note', () => {
- expect(
- findDesignNotes()
- .at(0)
- .isVisible(),
- ).toBe(true);
- expect(
- findDesignNotes()
- .at(1)
- .isVisible(),
- ).toBe(false);
- });
-
- it('renders resolved message', () => {
- expect(findResolvedMessage().exists()).toBe(true);
- });
-
- it('does not show renders reply placeholder', () => {
- expect(findReplyPlaceholder().isVisible()).toBe(false);
- });
-
- it('renders toggle replies widget with correct props', () => {
- expect(findRepliesWidget().exists()).toBe(true);
- expect(findRepliesWidget().props()).toEqual({
- collapsed: true,
- replies: notes.slice(1),
- });
- });
-
- it('renders a correct icon to resolve a thread', () => {
- expect(findResolveIcon().props('name')).toBe('check-circle-filled');
- });
-
- describe('when replies are expanded', () => {
- beforeEach(() => {
- findRepliesWidget().vm.$emit('toggle');
- return wrapper.vm.$nextTick();
- });
-
- it('renders replies widget with collapsed prop equal to false', () => {
- expect(findRepliesWidget().props('collapsed')).toBe(false);
- });
-
- it('renders the second note', () => {
- expect(
- findDesignNotes()
- .at(1)
- .isVisible(),
- ).toBe(true);
- });
-
- it('renders a reply placeholder', () => {
- expect(findReplyPlaceholder().isVisible()).toBe(true);
- });
-
- it('renders a checkbox with Unresolve thread text in reply form', () => {
- findReplyPlaceholder().vm.$emit('onClick');
- wrapper.setProps({ discussionWithOpenForm: discussion.id });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findResolveCheckbox().text()).toBe('Unresolve thread');
- });
- });
- });
- });
-
- it('hides reply placeholder and opens form on placeholder click', () => {
- createComponent();
- findReplyPlaceholder().vm.$emit('onClick');
- wrapper.setProps({ discussionWithOpenForm: discussion.id });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findReplyPlaceholder().exists()).toBe(false);
- expect(findReplyForm().exists()).toBe(true);
- });
- });
-
- it('calls mutation on submitting form and closes the form', () => {
- createComponent(
- { discussionWithOpenForm: discussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
-
- findReplyForm().vm.$emit('submitForm');
- expect(mutate).toHaveBeenCalledWith(mutationVariables);
-
- return mutate()
- .then(() => {
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findReplyForm().exists()).toBe(false);
- });
- });
-
- it('clears the discussion comment on closing comment form', () => {
- createComponent(
- { discussionWithOpenForm: discussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
-
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findReplyForm().vm.$emit('cancelForm');
-
- expect(wrapper.vm.discussionComment).toBe('');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findReplyForm().exists()).toBe(false);
- });
- });
-
- it('applies correct class to design notes when discussion is highlighted', () => {
- createComponent(
- {},
- {
- activeDiscussion: {
- id: notes[0].id,
- source: 'pin',
- },
- },
- );
-
- expect(wrapper.findAll(DesignNote).wrappers.every(note => note.classes('gl-bg-blue-50'))).toBe(
- true,
- );
- });
-
- it('calls toggleResolveDiscussion mutation on resolve thread button click', () => {
- createComponent();
- findResolveButton().trigger('click');
- expect(mutate).toHaveBeenCalledWith({
- mutation: toggleResolveDiscussionMutation,
- variables: {
- id: discussion.id,
- resolve: true,
- },
- });
- return wrapper.vm.$nextTick(() => {
- expect(findResolveLoadingIcon().exists()).toBe(true);
- });
- });
-
- it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
- createComponent(
- { discussionWithOpenForm: discussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
- findResolveButton().trigger('click');
- findReplyForm().vm.$emit('submitForm');
-
- return mutate().then(() => {
- expect(mutate).toHaveBeenCalledWith({
- mutation: toggleResolveDiscussionMutation,
- variables: {
- id: discussion.id,
- resolve: true,
- },
- });
- });
- });
-
- it('emits openForm event on opening the form', () => {
- createComponent();
- findReplyPlaceholder().vm.$emit('onClick');
-
- expect(wrapper.emitted('openForm')).toBeTruthy();
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_notes/design_note_spec.js b/spec/frontend/design_management_legacy/components/design_notes/design_note_spec.js
deleted file mode 100644
index aa187cd1388..00000000000
--- a/spec/frontend/design_management_legacy/components/design_notes/design_note_spec.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { ApolloMutation } from 'vue-apollo';
-import DesignNote from '~/design_management_legacy/components/design_notes/design_note.vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import DesignReplyForm from '~/design_management_legacy/components/design_notes/design_reply_form.vue';
-
-const scrollIntoViewMock = jest.fn();
-const note = {
- id: 'gid://gitlab/DiffNote/123',
- author: {
- id: 'author-id',
- },
- body: 'test',
- userPermissions: {
- adminNote: false,
- },
-};
-HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
-
-const $route = {
- hash: '#note_123',
-};
-
-const mutate = jest.fn().mockResolvedValue({ data: { updateNote: {} } });
-
-describe('Design note component', () => {
- let wrapper;
-
- const findUserAvatar = () => wrapper.find(UserAvatarLink);
- const findUserLink = () => wrapper.find('.js-user-link');
- const findReplyForm = () => wrapper.find(DesignReplyForm);
- const findEditButton = () => wrapper.find('.js-note-edit');
- const findNoteContent = () => wrapper.find('.js-note-text');
-
- function createComponent(props = {}, data = { isEditing: false }) {
- wrapper = shallowMount(DesignNote, {
- propsData: {
- note: {},
- ...props,
- },
- data() {
- return {
- ...data,
- };
- },
- mocks: {
- $route,
- $apollo: {
- mutate,
- },
- },
- stubs: {
- ApolloMutation,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('should match the snapshot', () => {
- createComponent({
- note,
- });
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('should render an author', () => {
- createComponent({
- note,
- });
-
- expect(findUserAvatar().exists()).toBe(true);
- expect(findUserLink().exists()).toBe(true);
- });
-
- it('should render a time ago tooltip if note has createdAt property', () => {
- createComponent({
- note: {
- ...note,
- createdAt: '2019-07-26T15:02:20Z',
- },
- });
-
- expect(wrapper.find(TimeAgoTooltip).exists()).toBe(true);
- });
-
- it('should trigger a scrollIntoView method', () => {
- createComponent({
- note,
- });
-
- expect(scrollIntoViewMock).toHaveBeenCalled();
- });
-
- it('should not render edit icon when user does not have a permission', () => {
- createComponent({
- note,
- });
-
- expect(findEditButton().exists()).toBe(false);
- });
-
- describe('when user has a permission to edit note', () => {
- it('should open an edit form on edit button click', () => {
- createComponent({
- note: {
- ...note,
- userPermissions: {
- adminNote: true,
- },
- },
- });
-
- findEditButton().trigger('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findReplyForm().exists()).toBe(true);
- expect(findNoteContent().exists()).toBe(false);
- });
- });
-
- describe('when edit form is rendered', () => {
- beforeEach(() => {
- createComponent(
- {
- note: {
- ...note,
- userPermissions: {
- adminNote: true,
- },
- },
- },
- { isEditing: true },
- );
- });
-
- it('should not render note content and should render reply form', () => {
- expect(findNoteContent().exists()).toBe(false);
- expect(findReplyForm().exists()).toBe(true);
- });
-
- it('hides the form on hideForm event', () => {
- findReplyForm().vm.$emit('cancelForm');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findReplyForm().exists()).toBe(false);
- expect(findNoteContent().exists()).toBe(true);
- });
- });
-
- it('calls a mutation on submitForm event and hides a form', () => {
- findReplyForm().vm.$emit('submitForm');
- expect(mutate).toHaveBeenCalled();
-
- return mutate()
- .then(() => {
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findReplyForm().exists()).toBe(false);
- expect(findNoteContent().exists()).toBe(true);
- });
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management_legacy/components/design_notes/design_reply_form_spec.js
deleted file mode 100644
index 088a71b64af..00000000000
--- a/spec/frontend/design_management_legacy/components/design_notes/design_reply_form_spec.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import { mount } from '@vue/test-utils';
-import DesignReplyForm from '~/design_management_legacy/components/design_notes/design_reply_form.vue';
-
-const showModal = jest.fn();
-
-const GlModal = {
- template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
- methods: {
- show: showModal,
- },
-};
-
-describe('Design reply form component', () => {
- let wrapper;
-
- const findTextarea = () => wrapper.find('textarea');
- const findSubmitButton = () => wrapper.find({ ref: 'submitButton' });
- const findCancelButton = () => wrapper.find({ ref: 'cancelButton' });
- const findModal = () => wrapper.find({ ref: 'cancelCommentModal' });
-
- function createComponent(props = {}, mountOptions = {}) {
- wrapper = mount(DesignReplyForm, {
- propsData: {
- value: '',
- isSaving: false,
- ...props,
- },
- stubs: { GlModal },
- ...mountOptions,
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('textarea has focus after component mount', () => {
- // We need to attach to document, so that `document.activeElement` is properly set in jsdom
- createComponent({}, { attachToDocument: true });
-
- expect(findTextarea().element).toEqual(document.activeElement);
- });
-
- it('renders button text as "Comment" when creating a comment', () => {
- createComponent();
-
- expect(findSubmitButton().html()).toMatchSnapshot();
- });
-
- it('renders button text as "Save comment" when creating a comment', () => {
- createComponent({ isNewComment: false });
-
- expect(findSubmitButton().html()).toMatchSnapshot();
- });
-
- describe('when form has no text', () => {
- beforeEach(() => {
- createComponent({
- value: '',
- });
- });
-
- it('submit button is disabled', () => {
- expect(findSubmitButton().attributes().disabled).toBeTruthy();
- });
-
- it('does not emit submitForm event on textarea ctrl+enter keydown', () => {
- findTextarea().trigger('keydown.enter', {
- ctrlKey: true,
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submitForm')).toBeFalsy();
- });
- });
-
- it('does not emit submitForm event on textarea meta+enter keydown', () => {
- findTextarea().trigger('keydown.enter', {
- metaKey: true,
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submitForm')).toBeFalsy();
- });
- });
-
- it('emits cancelForm event on pressing escape button on textarea', () => {
- findTextarea().trigger('keyup.esc');
-
- expect(wrapper.emitted('cancelForm')).toBeTruthy();
- });
-
- it('emits cancelForm event on clicking Cancel button', () => {
- findCancelButton().vm.$emit('click');
-
- expect(wrapper.emitted('cancelForm')).toHaveLength(1);
- });
- });
-
- describe('when form has text', () => {
- beforeEach(() => {
- createComponent({
- value: 'test',
- });
- });
-
- it('submit button is enabled', () => {
- expect(findSubmitButton().attributes().disabled).toBeFalsy();
- });
-
- it('emits submitForm event on Comment button click', () => {
- findSubmitButton().vm.$emit('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submitForm')).toBeTruthy();
- });
- });
-
- it('emits submitForm event on textarea ctrl+enter keydown', () => {
- findTextarea().trigger('keydown.enter', {
- ctrlKey: true,
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submitForm')).toBeTruthy();
- });
- });
-
- it('emits submitForm event on textarea meta+enter keydown', () => {
- findTextarea().trigger('keydown.enter', {
- metaKey: true,
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submitForm')).toBeTruthy();
- });
- });
-
- it('emits input event on changing textarea content', () => {
- findTextarea().setValue('test2');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('input')).toBeTruthy();
- });
- });
-
- it('emits cancelForm event on Escape key if text was not changed', () => {
- findTextarea().trigger('keyup.esc');
-
- expect(wrapper.emitted('cancelForm')).toBeTruthy();
- });
-
- it('opens confirmation modal on Escape key when text has changed', () => {
- wrapper.setProps({ value: 'test2' });
-
- return wrapper.vm.$nextTick().then(() => {
- findTextarea().trigger('keyup.esc');
- expect(showModal).toHaveBeenCalled();
- });
- });
-
- it('emits cancelForm event on Cancel button click if text was not changed', () => {
- findCancelButton().trigger('click');
-
- expect(wrapper.emitted('cancelForm')).toBeTruthy();
- });
-
- it('opens confirmation modal on Cancel button click when text has changed', () => {
- wrapper.setProps({ value: 'test2' });
-
- return wrapper.vm.$nextTick().then(() => {
- findCancelButton().trigger('click');
- expect(showModal).toHaveBeenCalled();
- });
- });
-
- it('emits cancelForm event on modal Ok button click', () => {
- findTextarea().trigger('keyup.esc');
- findModal().vm.$emit('ok');
-
- expect(wrapper.emitted('cancelForm')).toBeTruthy();
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_notes/toggle_replies_widget_spec.js b/spec/frontend/design_management_legacy/components/design_notes/toggle_replies_widget_spec.js
deleted file mode 100644
index acc7cbbca52..00000000000
--- a/spec/frontend/design_management_legacy/components/design_notes/toggle_replies_widget_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import ToggleRepliesWidget from '~/design_management_legacy/components/design_notes/toggle_replies_widget.vue';
-import notes from '../../mock_data/notes';
-
-describe('Toggle replies widget component', () => {
- let wrapper;
-
- const findToggleWrapper = () => wrapper.find('[data-testid="toggle-comments-wrapper"]');
- const findIcon = () => wrapper.find(GlIcon);
- const findButton = () => wrapper.find(GlButton);
- const findAuthorLink = () => wrapper.find(GlLink);
- const findTimeAgo = () => wrapper.find(TimeAgoTooltip);
-
- function createComponent(props = {}) {
- wrapper = shallowMount(ToggleRepliesWidget, {
- propsData: {
- collapsed: true,
- replies: notes,
- ...props,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when replies are collapsed', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('should not have expanded class', () => {
- expect(findToggleWrapper().classes()).not.toContain('expanded');
- });
-
- it('should render chevron-right icon', () => {
- expect(findIcon().props('name')).toBe('chevron-right');
- });
-
- it('should have replies length on button', () => {
- expect(findButton().text()).toBe('2 replies');
- });
-
- it('should render a link to the last reply author', () => {
- expect(findAuthorLink().exists()).toBe(true);
- expect(findAuthorLink().text()).toBe(notes[1].author.name);
- expect(findAuthorLink().attributes('href')).toBe(notes[1].author.webUrl);
- });
-
- it('should render correct time ago tooltip', () => {
- expect(findTimeAgo().exists()).toBe(true);
- expect(findTimeAgo().props('time')).toBe(notes[1].createdAt);
- });
- });
-
- describe('when replies are expanded', () => {
- beforeEach(() => {
- createComponent({ collapsed: false });
- });
-
- it('should have expanded class', () => {
- expect(findToggleWrapper().classes()).toContain('expanded');
- });
-
- it('should render chevron-down icon', () => {
- expect(findIcon().props('name')).toBe('chevron-down');
- });
-
- it('should have Collapse replies text on button', () => {
- expect(findButton().text()).toBe('Collapse replies');
- });
-
- it('should not have a link to the last reply author', () => {
- expect(findAuthorLink().exists()).toBe(false);
- });
-
- it('should not render time ago tooltip', () => {
- expect(findTimeAgo().exists()).toBe(false);
- });
- });
-
- it('should emit toggle event on icon click', () => {
- createComponent();
- findIcon().vm.$emit('click', new MouseEvent('click'));
-
- expect(wrapper.emitted('toggle')).toHaveLength(1);
- });
-
- it('should emit toggle event on button click', () => {
- createComponent();
- findButton().vm.$emit('click', new MouseEvent('click'));
-
- expect(wrapper.emitted('toggle')).toHaveLength(1);
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_overlay_spec.js b/spec/frontend/design_management_legacy/components/design_overlay_spec.js
deleted file mode 100644
index c014f3479f4..00000000000
--- a/spec/frontend/design_management_legacy/components/design_overlay_spec.js
+++ /dev/null
@@ -1,410 +0,0 @@
-import { mount } from '@vue/test-utils';
-import DesignOverlay from '~/design_management_legacy/components/design_overlay.vue';
-import updateActiveDiscussion from '~/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql';
-import notes from '../mock_data/notes';
-import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management_legacy/constants';
-
-const mutate = jest.fn(() => Promise.resolve());
-
-describe('Design overlay component', () => {
- let wrapper;
-
- const mockDimensions = { width: 100, height: 100 };
-
- const findOverlay = () => wrapper.find('.image-diff-overlay');
- const findAllNotes = () => wrapper.findAll('.js-image-badge');
- const findCommentBadge = () => wrapper.find('.comment-indicator');
- const findFirstBadge = () => findAllNotes().at(0);
- const findSecondBadge = () => findAllNotes().at(1);
-
- const clickAndDragBadge = (elem, fromPoint, toPoint) => {
- elem.trigger('mousedown', { clientX: fromPoint.x, clientY: fromPoint.y });
- return wrapper.vm.$nextTick().then(() => {
- elem.trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y });
- return wrapper.vm.$nextTick();
- });
- };
-
- function createComponent(props = {}, data = {}) {
- wrapper = mount(DesignOverlay, {
- propsData: {
- dimensions: mockDimensions,
- position: {
- top: '0',
- left: '0',
- },
- resolvedDiscussionsExpanded: false,
- ...props,
- },
- data() {
- return {
- activeDiscussion: {
- id: null,
- source: null,
- },
- ...data,
- };
- },
- mocks: {
- $apollo: {
- mutate,
- },
- },
- });
- }
-
- it('should have correct inline style', () => {
- createComponent();
-
- expect(wrapper.find('.image-diff-overlay').attributes().style).toBe(
- 'width: 100px; height: 100px; top: 0px; left: 0px;',
- );
- });
-
- it('should emit `openCommentForm` when clicking on overlay', () => {
- createComponent();
- const newCoordinates = {
- x: 10,
- y: 10,
- };
-
- wrapper
- .find('.image-diff-overlay-add-comment')
- .trigger('mouseup', { offsetX: newCoordinates.x, offsetY: newCoordinates.y });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('openCommentForm')).toEqual([
- [{ x: newCoordinates.x, y: newCoordinates.y }],
- ]);
- });
- });
-
- describe('with notes', () => {
- it('should render only the first note', () => {
- createComponent({
- notes,
- });
- expect(findAllNotes()).toHaveLength(1);
- });
-
- describe('with resolved discussions toggle expanded', () => {
- beforeEach(() => {
- createComponent({
- notes,
- resolvedDiscussionsExpanded: true,
- });
- });
-
- it('should render all notes', () => {
- expect(findAllNotes()).toHaveLength(notes.length);
- });
-
- it('should have set the correct position for each note badge', () => {
- expect(findFirstBadge().attributes().style).toBe('left: 10px; top: 15px;');
- expect(findSecondBadge().attributes().style).toBe('left: 50px; top: 50px;');
- });
-
- it('should apply resolved class to the resolved note pin', () => {
- expect(findSecondBadge().classes()).toContain('resolved');
- });
-
- it('when there is an active discussion, should apply inactive class to all pins besides the active one', () => {
- wrapper.setData({
- activeDiscussion: {
- id: notes[0].id,
- source: 'discussion',
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findSecondBadge().classes()).toContain('inactive');
- });
- });
- });
-
- it('should recalculate badges positions on window resize', () => {
- createComponent({
- notes,
- dimensions: {
- width: 400,
- height: 400,
- },
- });
-
- expect(findFirstBadge().attributes().style).toBe('left: 40px; top: 60px;');
-
- wrapper.setProps({
- dimensions: {
- width: 200,
- height: 200,
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 30px;');
- });
- });
-
- it('should call an update active discussion mutation when clicking a note without moving it', () => {
- const note = notes[0];
- const { position } = note;
- const mutationVariables = {
- mutation: updateActiveDiscussion,
- variables: {
- id: note.id,
- source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin,
- },
- };
-
- findFirstBadge().trigger('mousedown', { clientX: position.x, clientY: position.y });
-
- return wrapper.vm.$nextTick().then(() => {
- findFirstBadge().trigger('mouseup', { clientX: position.x, clientY: position.y });
- expect(mutate).toHaveBeenCalledWith(mutationVariables);
- });
- });
- });
-
- describe('when moving notes', () => {
- it('should update badge style when note is being moved', () => {
- createComponent({
- notes,
- });
-
- const { position } = notes[0];
-
- return clickAndDragBadge(
- findFirstBadge(),
- { x: position.x, y: position.y },
- { x: 20, y: 20 },
- ).then(() => {
- expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px; cursor: move;');
- });
- });
-
- it('should emit `moveNote` event when note-moving action ends', () => {
- createComponent({ notes });
- const note = notes[0];
- const { position } = note;
- const newCoordinates = { x: 20, y: 20 };
-
- wrapper.setData({
- movingNoteNewPosition: {
- ...position,
- ...newCoordinates,
- },
- movingNoteStartPosition: {
- noteId: notes[0].id,
- discussionId: notes[0].discussion.id,
- ...position,
- },
- });
-
- const badge = findFirstBadge();
- return clickAndDragBadge(badge, { x: position.x, y: position.y }, newCoordinates)
- .then(() => {
- badge.trigger('mouseup');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('moveNote')).toEqual([
- [
- {
- noteId: notes[0].id,
- discussionId: notes[0].discussion.id,
- coordinates: newCoordinates,
- },
- ],
- ]);
- });
- });
-
- describe('without [adminNote] permission', () => {
- const mockNoteNotAuthorised = {
- ...notes[0],
- userPermissions: {
- adminNote: false,
- },
- };
-
- const mockNoteCoordinates = {
- x: mockNoteNotAuthorised.position.x,
- y: mockNoteNotAuthorised.position.y,
- };
-
- it('should be unable to move a note', () => {
- createComponent({
- dimensions: mockDimensions,
- notes: [mockNoteNotAuthorised],
- });
-
- const badge = findAllNotes().at(0);
- return clickAndDragBadge(badge, { ...mockNoteCoordinates }, { x: 20, y: 20 }).then(() => {
- // note position should not change after a click-and-drag attempt
- expect(findFirstBadge().attributes().style).toContain(
- `left: ${mockNoteCoordinates.x}px; top: ${mockNoteCoordinates.y}px;`,
- );
- });
- });
- });
- });
-
- describe('with a new form', () => {
- it('should render a new comment badge', () => {
- createComponent({
- currentCommentForm: {
- ...notes[0].position,
- },
- });
-
- expect(findCommentBadge().exists()).toBe(true);
- expect(findCommentBadge().attributes().style).toBe('left: 10px; top: 15px;');
- });
-
- describe('when moving the comment badge', () => {
- it('should update badge style to reflect new position', () => {
- const { position } = notes[0];
-
- createComponent({
- currentCommentForm: {
- ...position,
- },
- });
-
- return clickAndDragBadge(
- findCommentBadge(),
- { x: position.x, y: position.y },
- { x: 20, y: 20 },
- ).then(() => {
- expect(findCommentBadge().attributes().style).toBe(
- 'left: 20px; top: 20px; cursor: move;',
- );
- });
- });
-
- it('should update badge style when note-moving action ends', () => {
- const { position } = notes[0];
- createComponent({
- currentCommentForm: {
- ...position,
- },
- });
-
- const commentBadge = findCommentBadge();
- const toPoint = { x: 20, y: 20 };
-
- return clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint)
- .then(() => {
- commentBadge.trigger('mouseup');
- // simulates the currentCommentForm being updated in index.vue component, and
- // propagated back down to this prop
- wrapper.setProps({
- currentCommentForm: { height: position.height, width: position.width, ...toPoint },
- });
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(commentBadge.attributes().style).toBe('left: 20px; top: 20px;');
- });
- });
-
- it.each`
- element | getElementFunc | event
- ${'overlay'} | ${findOverlay} | ${'mouseleave'}
- ${'comment badge'} | ${findCommentBadge} | ${'mouseup'}
- `(
- 'should emit `openCommentForm` event when $event fired on $element element',
- ({ getElementFunc, event }) => {
- createComponent({
- notes,
- currentCommentForm: {
- ...notes[0].position,
- },
- });
-
- const newCoordinates = { x: 20, y: 20 };
- wrapper.setData({
- movingNoteStartPosition: {
- ...notes[0].position,
- },
- movingNoteNewPosition: {
- ...notes[0].position,
- ...newCoordinates,
- },
- });
-
- getElementFunc().trigger(event);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]);
- });
- },
- );
- });
- });
-
- describe('getMovingNotePositionDelta', () => {
- it('should calculate delta correctly from state', () => {
- createComponent();
-
- wrapper.setData({
- movingNoteStartPosition: {
- clientX: 10,
- clientY: 20,
- },
- });
-
- const mockMouseEvent = {
- clientX: 30,
- clientY: 10,
- };
-
- expect(wrapper.vm.getMovingNotePositionDelta(mockMouseEvent)).toEqual({
- deltaX: 20,
- deltaY: -10,
- });
- });
- });
-
- describe('isPositionInOverlay', () => {
- createComponent({ dimensions: mockDimensions });
-
- it.each`
- test | coordinates | expectedResult
- ${'within overlay bounds'} | ${{ x: 50, y: 50 }} | ${true}
- ${'outside overlay bounds'} | ${{ x: 101, y: 101 }} | ${false}
- `('returns [$expectedResult] when position is $test', ({ coordinates, expectedResult }) => {
- const position = { ...mockDimensions, ...coordinates };
-
- expect(wrapper.vm.isPositionInOverlay(position)).toBe(expectedResult);
- });
- });
-
- describe('getNoteRelativePosition', () => {
- it('calculates position correctly', () => {
- createComponent({ dimensions: mockDimensions });
- const position = { x: 50, y: 50, width: 200, height: 200 };
-
- expect(wrapper.vm.getNoteRelativePosition(position)).toEqual({ left: 25, top: 25 });
- });
- });
-
- describe('canMoveNote', () => {
- it.each`
- adminNotePermission | canMoveNoteResult
- ${true} | ${true}
- ${false} | ${false}
- ${undefined} | ${false}
- `(
- 'returns [$canMoveNoteResult] when [adminNote permission] is [$adminNotePermission]',
- ({ adminNotePermission, canMoveNoteResult }) => {
- createComponent();
-
- const note = {
- userPermissions: {
- adminNote: adminNotePermission,
- },
- };
- expect(wrapper.vm.canMoveNote(note)).toBe(canMoveNoteResult);
- },
- );
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_presentation_spec.js b/spec/frontend/design_management_legacy/components/design_presentation_spec.js
deleted file mode 100644
index ceff86b0549..00000000000
--- a/spec/frontend/design_management_legacy/components/design_presentation_spec.js
+++ /dev/null
@@ -1,553 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import DesignPresentation from '~/design_management_legacy/components/design_presentation.vue';
-import DesignOverlay from '~/design_management_legacy/components/design_overlay.vue';
-
-const mockOverlayData = {
- overlayDimensions: {
- width: 100,
- height: 100,
- },
- overlayPosition: {
- top: '0',
- left: '0',
- },
-};
-
-describe('Design management design presentation component', () => {
- let wrapper;
-
- function createComponent(
- {
- image,
- imageName,
- discussions = [],
- isAnnotating = false,
- resolvedDiscussionsExpanded = false,
- } = {},
- data = {},
- stubs = {},
- ) {
- wrapper = shallowMount(DesignPresentation, {
- propsData: {
- image,
- imageName,
- discussions,
- isAnnotating,
- resolvedDiscussionsExpanded,
- },
- stubs,
- });
-
- wrapper.setData(data);
- wrapper.element.scrollTo = jest.fn();
- }
-
- const findOverlayCommentButton = () => wrapper.find('.image-diff-overlay-add-comment');
-
- /**
- * Spy on $refs and mock given values
- * @param {Object} viewportDimensions {width, height}
- * @param {Object} childDimensions {width, height}
- * @param {Float} scrollTopPerc 0 < x < 1
- * @param {Float} scrollLeftPerc 0 < x < 1
- */
- function mockRefDimensions(
- ref,
- viewportDimensions,
- childDimensions,
- scrollTopPerc,
- scrollLeftPerc,
- ) {
- jest.spyOn(ref, 'scrollWidth', 'get').mockReturnValue(childDimensions.width);
- jest.spyOn(ref, 'scrollHeight', 'get').mockReturnValue(childDimensions.height);
- jest.spyOn(ref, 'offsetWidth', 'get').mockReturnValue(viewportDimensions.width);
- jest.spyOn(ref, 'offsetHeight', 'get').mockReturnValue(viewportDimensions.height);
- jest
- .spyOn(ref, 'scrollLeft', 'get')
- .mockReturnValue((childDimensions.width - viewportDimensions.width) * scrollLeftPerc);
- jest
- .spyOn(ref, 'scrollTop', 'get')
- .mockReturnValue((childDimensions.height - viewportDimensions.height) * scrollTopPerc);
- }
-
- function clickDragExplore(startCoords, endCoords, { useTouchEvents, mouseup } = {}) {
- const event = useTouchEvents
- ? {
- mousedown: 'touchstart',
- mousemove: 'touchmove',
- mouseup: 'touchend',
- }
- : {
- mousedown: 'mousedown',
- mousemove: 'mousemove',
- mouseup: 'mouseup',
- };
-
- const addCommentOverlay = findOverlayCommentButton();
-
- // triggering mouse events on this element best simulates
- // reality, as it is the lowest-level node that needs to
- // respond to mouse events
- addCommentOverlay.trigger(event.mousedown, {
- clientX: startCoords.clientX,
- clientY: startCoords.clientY,
- });
- return wrapper.vm
- .$nextTick()
- .then(() => {
- addCommentOverlay.trigger(event.mousemove, {
- clientX: endCoords.clientX,
- clientY: endCoords.clientY,
- });
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- if (mouseup) {
- addCommentOverlay.trigger(event.mouseup);
- return wrapper.vm.$nextTick();
- }
-
- return undefined;
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders image and overlay when image provided', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- );
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders empty state when no image provided', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('openCommentForm event emits correct data', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- );
-
- wrapper.vm.openCommentForm({ x: 1, y: 1 });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('openCommentForm')).toEqual([
- [{ ...mockOverlayData.overlayDimensions, x: 1, y: 1 }],
- ]);
- });
- });
-
- describe('currentCommentForm', () => {
- it('is null when isAnnotating is false', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- );
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.currentCommentForm).toBeNull();
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('is null when isAnnotating is true but annotation position is falsey', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- isAnnotating: true,
- },
- mockOverlayData,
- );
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.currentCommentForm).toBeNull();
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('is equal to current annotation position when isAnnotating is true', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- isAnnotating: true,
- },
- {
- ...mockOverlayData,
- currentAnnotationPosition: {
- x: 1,
- y: 1,
- width: 100,
- height: 100,
- },
- },
- );
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.currentCommentForm).toEqual({
- x: 1,
- y: 1,
- width: 100,
- height: 100,
- });
- expect(wrapper.element).toMatchSnapshot();
- });
- });
- });
-
- describe('setOverlayPosition', () => {
- beforeEach(() => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('sets overlay position correctly when overlay is smaller than viewport', () => {
- jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(200);
- jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(200);
-
- wrapper.vm.setOverlayPosition();
- expect(wrapper.vm.overlayPosition).toEqual({
- left: `calc(50% - ${mockOverlayData.overlayDimensions.width / 2}px)`,
- top: `calc(50% - ${mockOverlayData.overlayDimensions.height / 2}px)`,
- });
- });
-
- it('sets overlay position correctly when overlay width is larger than viewports', () => {
- jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(50);
- jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(200);
-
- wrapper.vm.setOverlayPosition();
- expect(wrapper.vm.overlayPosition).toEqual({
- left: '0',
- top: `calc(50% - ${mockOverlayData.overlayDimensions.height / 2}px)`,
- });
- });
-
- it('sets overlay position correctly when overlay height is larger than viewports', () => {
- jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(200);
- jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(50);
-
- wrapper.vm.setOverlayPosition();
- expect(wrapper.vm.overlayPosition).toEqual({
- left: `calc(50% - ${mockOverlayData.overlayDimensions.width / 2}px)`,
- top: '0',
- });
- });
- });
-
- describe('getViewportCenter', () => {
- beforeEach(() => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- );
- });
-
- it('calculate center correctly with no scroll', () => {
- mockRefDimensions(
- wrapper.vm.$refs.presentationViewport,
- { width: 10, height: 10 },
- { width: 20, height: 20 },
- 0,
- 0,
- );
-
- expect(wrapper.vm.getViewportCenter()).toEqual({
- x: 5,
- y: 5,
- });
- });
-
- it('calculate center correctly with some scroll', () => {
- mockRefDimensions(
- wrapper.vm.$refs.presentationViewport,
- { width: 10, height: 10 },
- { width: 20, height: 20 },
- 0.5,
- 0.5,
- );
-
- expect(wrapper.vm.getViewportCenter()).toEqual({
- x: 10,
- y: 10,
- });
- });
-
- it('Returns default case if no overflow (scrollWidth==offsetWidth, etc.)', () => {
- mockRefDimensions(
- wrapper.vm.$refs.presentationViewport,
- { width: 20, height: 20 },
- { width: 20, height: 20 },
- 0.5,
- 0.5,
- );
-
- expect(wrapper.vm.getViewportCenter()).toEqual({
- x: 10,
- y: 10,
- });
- });
- });
-
- describe('scaleZoomFocalPoint', () => {
- it('scales focal point correctly when zooming in', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- {
- ...mockOverlayData,
- zoomFocalPoint: {
- x: 5,
- y: 5,
- width: 50,
- height: 50,
- },
- },
- );
-
- wrapper.vm.scaleZoomFocalPoint();
- expect(wrapper.vm.zoomFocalPoint).toEqual({
- x: 10,
- y: 10,
- width: 100,
- height: 100,
- });
- });
-
- it('scales focal point correctly when zooming out', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- {
- ...mockOverlayData,
- zoomFocalPoint: {
- x: 10,
- y: 10,
- width: 200,
- height: 200,
- },
- },
- );
-
- wrapper.vm.scaleZoomFocalPoint();
- expect(wrapper.vm.zoomFocalPoint).toEqual({
- x: 5,
- y: 5,
- width: 100,
- height: 100,
- });
- });
- });
-
- describe('onImageResize', () => {
- it('sets zoom focal point on initial load', () => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- );
-
- wrapper.setMethods({
- shiftZoomFocalPoint: jest.fn(),
- scaleZoomFocalPoint: jest.fn(),
- scrollToFocalPoint: jest.fn(),
- });
-
- wrapper.vm.onImageResize({ width: 10, height: 10 });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.shiftZoomFocalPoint).toHaveBeenCalled();
- expect(wrapper.vm.initialLoad).toBe(false);
- });
- });
-
- it('calls scaleZoomFocalPoint and scrollToFocalPoint after initial load', () => {
- wrapper.vm.onImageResize({ width: 10, height: 10 });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.scaleZoomFocalPoint).toHaveBeenCalled();
- expect(wrapper.vm.scrollToFocalPoint).toHaveBeenCalled();
- });
- });
- });
-
- describe('onPresentationMousedown', () => {
- it.each`
- scenario | width | height
- ${'width overflows'} | ${101} | ${100}
- ${'height overflows'} | ${100} | ${101}
- ${'width and height overflows'} | ${200} | ${200}
- `('sets lastDragPosition when design $scenario', ({ width, height }) => {
- createComponent();
- mockRefDimensions(
- wrapper.vm.$refs.presentationViewport,
- { width: 100, height: 100 },
- { width, height },
- );
-
- const newLastDragPosition = { x: 2, y: 2 };
- wrapper.vm.onPresentationMousedown({
- clientX: newLastDragPosition.x,
- clientY: newLastDragPosition.y,
- });
-
- expect(wrapper.vm.lastDragPosition).toStrictEqual(newLastDragPosition);
- });
-
- it('does not set lastDragPosition if design does not overflow', () => {
- const lastDragPosition = { x: 1, y: 1 };
-
- createComponent({}, { lastDragPosition });
- mockRefDimensions(
- wrapper.vm.$refs.presentationViewport,
- { width: 100, height: 100 },
- { width: 50, height: 50 },
- );
-
- wrapper.vm.onPresentationMousedown({ clientX: 2, clientY: 2 });
-
- // check lastDragPosition is unchanged
- expect(wrapper.vm.lastDragPosition).toStrictEqual(lastDragPosition);
- });
- });
-
- describe('getAnnotationPositon', () => {
- it.each`
- coordinates | overlayDimensions | position
- ${{ x: 100, y: 100 }} | ${{ width: 50, height: 50 }} | ${{ x: 100, y: 100, width: 50, height: 50 }}
- ${{ x: 100.2, y: 100.5 }} | ${{ width: 50.6, height: 50.0 }} | ${{ x: 100, y: 101, width: 51, height: 50 }}
- `('returns correct annotation position', ({ coordinates, overlayDimensions, position }) => {
- createComponent(undefined, {
- overlayDimensions: {
- width: overlayDimensions.width,
- height: overlayDimensions.height,
- },
- });
-
- expect(wrapper.vm.getAnnotationPositon(coordinates)).toStrictEqual(position);
- });
- });
-
- describe('when design is overflowing', () => {
- beforeEach(() => {
- createComponent(
- {
- image: 'test.jpg',
- imageName: 'test',
- },
- mockOverlayData,
- {
- 'design-overlay': DesignOverlay,
- },
- );
-
- // mock a design that overflows
- mockRefDimensions(
- wrapper.vm.$refs.presentationViewport,
- { width: 10, height: 10 },
- { width: 20, height: 20 },
- 0,
- 0,
- );
- });
-
- it('opens a comment form if design was not dragged', () => {
- const addCommentOverlay = findOverlayCommentButton();
- const startCoords = {
- clientX: 1,
- clientY: 1,
- };
-
- addCommentOverlay.trigger('mousedown', {
- clientX: startCoords.clientX,
- clientY: startCoords.clientY,
- });
-
- return wrapper.vm
- .$nextTick()
- .then(() => {
- addCommentOverlay.trigger('mouseup');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('openCommentForm')).toBeDefined();
- });
- });
-
- describe('when clicking and dragging', () => {
- it.each`
- description | useTouchEvents
- ${'with touch events'} | ${true}
- ${'without touch events'} | ${false}
- `('calls scrollTo with correct arguments $description', ({ useTouchEvents }) => {
- return clickDragExplore(
- { clientX: 0, clientY: 0 },
- { clientX: 10, clientY: 10 },
- { useTouchEvents },
- ).then(() => {
- expect(wrapper.element.scrollTo).toHaveBeenCalledTimes(1);
- expect(wrapper.element.scrollTo).toHaveBeenCalledWith(-10, -10);
- });
- });
-
- it('does not open a comment form when drag position exceeds buffer', () => {
- return clickDragExplore(
- { clientX: 0, clientY: 0 },
- { clientX: 10, clientY: 10 },
- { mouseup: true },
- ).then(() => {
- expect(wrapper.emitted('openCommentForm')).toBeFalsy();
- });
- });
-
- it('opens a comment form when drag position is within buffer', () => {
- return clickDragExplore(
- { clientX: 0, clientY: 0 },
- { clientX: 1, clientY: 0 },
- { mouseup: true },
- ).then(() => {
- expect(wrapper.emitted('openCommentForm')).toBeDefined();
- });
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_scaler_spec.js b/spec/frontend/design_management_legacy/components/design_scaler_spec.js
deleted file mode 100644
index 30ef5ab159b..00000000000
--- a/spec/frontend/design_management_legacy/components/design_scaler_spec.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import DesignScaler from '~/design_management_legacy/components/design_scaler.vue';
-
-describe('Design management design scaler component', () => {
- let wrapper;
-
- function createComponent(propsData, data = {}) {
- wrapper = shallowMount(DesignScaler, {
- propsData,
- });
- wrapper.setData(data);
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const getButton = type => {
- const buttonTypeOrder = ['minus', 'reset', 'plus'];
- const buttons = wrapper.findAll('button');
- return buttons.at(buttonTypeOrder.indexOf(type));
- };
-
- it('emits @scale event when "plus" button clicked', () => {
- createComponent();
-
- getButton('plus').trigger('click');
- expect(wrapper.emitted('scale')).toEqual([[1.2]]);
- });
-
- it('emits @scale event when "reset" button clicked (scale > 1)', () => {
- createComponent({}, { scale: 1.6 });
- return wrapper.vm.$nextTick().then(() => {
- getButton('reset').trigger('click');
- expect(wrapper.emitted('scale')).toEqual([[1]]);
- });
- });
-
- it('emits @scale event when "minus" button clicked (scale > 1)', () => {
- createComponent({}, { scale: 1.6 });
-
- return wrapper.vm.$nextTick().then(() => {
- getButton('minus').trigger('click');
- expect(wrapper.emitted('scale')).toEqual([[1.4]]);
- });
- });
-
- it('minus and reset buttons are disabled when scale === 1', () => {
- createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('minus and reset buttons are enabled when scale > 1', () => {
- createComponent({}, { scale: 1.2 });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('plus button is disabled when scale === 2', () => {
- createComponent({}, { scale: 2 });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/design_sidebar_spec.js b/spec/frontend/design_management_legacy/components/design_sidebar_spec.js
deleted file mode 100644
index fc0f618c359..00000000000
--- a/spec/frontend/design_management_legacy/components/design_sidebar_spec.js
+++ /dev/null
@@ -1,236 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlCollapse, GlPopover } from '@gitlab/ui';
-import Cookies from 'js-cookie';
-import DesignSidebar from '~/design_management_legacy/components/design_sidebar.vue';
-import Participants from '~/sidebar/components/participants/participants.vue';
-import DesignDiscussion from '~/design_management_legacy/components/design_notes/design_discussion.vue';
-import design from '../mock_data/design';
-import updateActiveDiscussionMutation from '~/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql';
-
-const updateActiveDiscussionMutationVariables = {
- mutation: updateActiveDiscussionMutation,
- variables: {
- id: design.discussions.nodes[0].notes.nodes[0].id,
- source: 'discussion',
- },
-};
-
-const $route = {
- params: {
- id: '1',
- },
-};
-
-const cookieKey = 'hide_design_resolved_comments_popover';
-
-const mutate = jest.fn().mockResolvedValue();
-
-describe('Design management design sidebar component', () => {
- let wrapper;
-
- const findDiscussions = () => wrapper.findAll(DesignDiscussion);
- const findFirstDiscussion = () => findDiscussions().at(0);
- const findUnresolvedDiscussions = () => wrapper.findAll('[data-testid="unresolved-discussion"]');
- const findResolvedDiscussions = () => wrapper.findAll('[data-testid="resolved-discussion"]');
- const findParticipants = () => wrapper.find(Participants);
- const findCollapsible = () => wrapper.find(GlCollapse);
- const findToggleResolvedCommentsButton = () => wrapper.find('[data-testid="resolved-comments"]');
- const findPopover = () => wrapper.find(GlPopover);
- const findNewDiscussionDisclaimer = () =>
- wrapper.find('[data-testid="new-discussion-disclaimer"]');
-
- function createComponent(props = {}) {
- wrapper = shallowMount(DesignSidebar, {
- propsData: {
- design,
- resolvedDiscussionsExpanded: false,
- markdownPreviewPath: '',
- ...props,
- },
- mocks: {
- $route,
- $apollo: {
- mutate,
- },
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders participants', () => {
- createComponent();
-
- expect(findParticipants().exists()).toBe(true);
- });
-
- it('passes the correct amount of participants to the Participants component', () => {
- createComponent();
-
- expect(findParticipants().props('participants')).toHaveLength(1);
- });
-
- describe('when has no discussions', () => {
- beforeEach(() => {
- createComponent({
- design: {
- ...design,
- discussions: {
- nodes: [],
- },
- },
- });
- });
-
- it('does not render discussions', () => {
- expect(findDiscussions().exists()).toBe(false);
- });
-
- it('renders a message about possibility to create a new discussion', () => {
- expect(findNewDiscussionDisclaimer().exists()).toBe(true);
- });
- });
-
- describe('when has discussions', () => {
- beforeEach(() => {
- Cookies.set(cookieKey, true);
- createComponent();
- });
-
- it('renders correct amount of unresolved discussions', () => {
- expect(findUnresolvedDiscussions()).toHaveLength(1);
- });
-
- it('renders correct amount of resolved discussions', () => {
- expect(findResolvedDiscussions()).toHaveLength(1);
- });
-
- it('has resolved comments collapsible collapsed', () => {
- expect(findCollapsible().attributes('visible')).toBeUndefined();
- });
-
- it('emits toggleResolveComments event on resolve comments button click', () => {
- findToggleResolvedCommentsButton().vm.$emit('click');
- expect(wrapper.emitted('toggleResolvedComments')).toHaveLength(1);
- });
-
- it('opens a collapsible when resolvedDiscussionsExpanded prop changes to true', () => {
- expect(findCollapsible().attributes('visible')).toBeUndefined();
- wrapper.setProps({
- resolvedDiscussionsExpanded: true,
- });
- return wrapper.vm.$nextTick().then(() => {
- expect(findCollapsible().attributes('visible')).toBe('true');
- });
- });
-
- it('does not popover about resolved comments', () => {
- expect(findPopover().exists()).toBe(false);
- });
-
- it('sends a mutation to set an active discussion when clicking on a discussion', () => {
- findFirstDiscussion().trigger('click');
-
- expect(mutate).toHaveBeenCalledWith(updateActiveDiscussionMutationVariables);
- });
-
- it('sends a mutation to reset an active discussion when clicking outside of discussion', () => {
- wrapper.trigger('click');
-
- expect(mutate).toHaveBeenCalledWith({
- ...updateActiveDiscussionMutationVariables,
- variables: { id: undefined, source: 'discussion' },
- });
- });
-
- it('emits correct event on discussion create note error', () => {
- findFirstDiscussion().vm.$emit('createNoteError', 'payload');
- expect(wrapper.emitted('onDesignDiscussionError')).toEqual([['payload']]);
- });
-
- it('emits correct event on discussion update note error', () => {
- findFirstDiscussion().vm.$emit('updateNoteError', 'payload');
- expect(wrapper.emitted('updateNoteError')).toEqual([['payload']]);
- });
-
- it('emits correct event on discussion resolve error', () => {
- findFirstDiscussion().vm.$emit('resolveDiscussionError', 'payload');
- expect(wrapper.emitted('resolveDiscussionError')).toEqual([['payload']]);
- });
-
- it('changes prop correctly on opening discussion form', () => {
- findFirstDiscussion().vm.$emit('openForm', 'some-id');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findFirstDiscussion().props('discussionWithOpenForm')).toBe('some-id');
- });
- });
- });
-
- describe('when all discussions are resolved', () => {
- beforeEach(() => {
- createComponent({
- design: {
- ...design,
- discussions: {
- nodes: [
- {
- id: 'discussion-id',
- replyId: 'discussion-reply-id',
- resolved: true,
- notes: {
- nodes: [
- {
- id: 'note-id',
- body: '123',
- author: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'link-to-author',
- avatarUrl: 'link-to-avatar',
- },
- },
- ],
- },
- },
- ],
- },
- },
- });
- });
-
- it('renders a message about possibility to create a new discussion', () => {
- expect(findNewDiscussionDisclaimer().exists()).toBe(true);
- });
-
- it('does not render unresolved discussions', () => {
- expect(findUnresolvedDiscussions()).toHaveLength(0);
- });
- });
-
- describe('when showing resolved discussions for the first time', () => {
- beforeEach(() => {
- Cookies.set(cookieKey, false);
- createComponent();
- });
-
- it('renders a popover if we show resolved comments collapsible for the first time', () => {
- expect(findPopover().exists()).toBe(true);
- });
-
- it('dismisses a popover on the outside click', () => {
- wrapper.trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(findPopover().exists()).toBe(false);
- });
- });
-
- it(`sets a ${cookieKey} cookie on clicking outside the popover`, () => {
- jest.spyOn(Cookies, 'set');
- wrapper.trigger('click');
- expect(Cookies.set).toHaveBeenCalledWith(cookieKey, 'true', { expires: 365 * 10 });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/image_spec.js b/spec/frontend/design_management_legacy/components/image_spec.js
deleted file mode 100644
index 265c91abb4e..00000000000
--- a/spec/frontend/design_management_legacy/components/image_spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlIcon } from '@gitlab/ui';
-import DesignImage from '~/design_management_legacy/components/image.vue';
-
-describe('Design management large image component', () => {
- let wrapper;
-
- function createComponent(propsData, data = {}) {
- wrapper = shallowMount(DesignImage, {
- propsData,
- });
- wrapper.setData(data);
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders loading state', () => {
- createComponent({
- isLoading: true,
- });
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders image', () => {
- createComponent({
- isLoading: false,
- image: 'test.jpg',
- name: 'test',
- });
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('sets correct classes and styles if imageStyle is set', () => {
- createComponent(
- {
- isLoading: false,
- image: 'test.jpg',
- name: 'test',
- },
- {
- imageStyle: {
- width: '100px',
- height: '100px',
- },
- },
- );
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders media broken icon on error', () => {
- createComponent({
- isLoading: false,
- image: 'test.jpg',
- name: 'test',
- });
-
- const image = wrapper.find('img');
- image.trigger('error');
- return wrapper.vm.$nextTick().then(() => {
- expect(image.isVisible()).toBe(false);
- expect(wrapper.find(GlIcon).element).toMatchSnapshot();
- });
- });
-
- describe('zoom', () => {
- const baseImageWidth = 100;
- const baseImageHeight = 100;
-
- beforeEach(() => {
- createComponent(
- {
- isLoading: false,
- image: 'test.jpg',
- name: 'test',
- },
- {
- imageStyle: {
- width: `${baseImageWidth}px`,
- height: `${baseImageHeight}px`,
- },
- baseImageSize: {
- width: baseImageWidth,
- height: baseImageHeight,
- },
- },
- );
-
- jest.spyOn(wrapper.vm.$refs.contentImg, 'offsetWidth', 'get').mockReturnValue(baseImageWidth);
- jest
- .spyOn(wrapper.vm.$refs.contentImg, 'offsetHeight', 'get')
- .mockReturnValue(baseImageHeight);
- });
-
- it('emits @resize event on zoom', () => {
- const zoomAmount = 2;
- wrapper.vm.zoom(zoomAmount);
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('resize')).toEqual([
- [{ width: baseImageWidth * zoomAmount, height: baseImageHeight * zoomAmount }],
- ]);
- });
- });
-
- it('emits @resize event with base image size when scale=1', () => {
- wrapper.vm.zoom(1);
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('resize')).toEqual([
- [{ width: baseImageWidth, height: baseImageHeight }],
- ]);
- });
- });
-
- it('sets image style when zoomed', () => {
- const zoomAmount = 2;
- wrapper.vm.zoom(zoomAmount);
- expect(wrapper.vm.imageStyle).toEqual({
- width: `${baseImageWidth * zoomAmount}px`,
- height: `${baseImageHeight * zoomAmount}px`,
- });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management_legacy/components/list/__snapshots__/item_spec.js.snap
deleted file mode 100644
index 003c8bc3c16..00000000000
--- a/spec/frontend/design_management_legacy/components/list/__snapshots__/item_spec.js.snap
+++ /dev/null
@@ -1,149 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management list item component when item appears in view after image is loaded renders media broken icon when image onerror triggered 1`] = `
-<gl-icon-stub
- class="text-secondary"
- name="media-broken"
- size="32"
-/>
-`;
-
-exports[`Design management list item component with notes renders item with multiple comments 1`] = `
-<router-link-stub
- class="card cursor-pointer text-plain js-design-list-item design-list-item"
- to="[object Object]"
->
- <div
- class="card-body p-0 d-flex-center overflow-hidden position-relative"
- >
- <!---->
-
- <gl-intersection-observer-stub>
- <!---->
-
- <img
- alt="test"
- class="block mx-auto mw-100 mh-100 design-img"
- data-qa-selector="design_image"
- src=""
- />
- </gl-intersection-observer-stub>
- </div>
-
- <div
- class="card-footer d-flex w-100"
- >
- <div
- class="d-flex flex-column str-truncated-100"
- >
- <span
- class="bold str-truncated-100"
- data-qa-selector="design_file_name"
- >
- test
- </span>
-
- <span
- class="str-truncated-100"
- >
-
- Updated
- <timeago-stub
- cssclass=""
- time="01-01-2019"
- tooltipplacement="bottom"
- />
- </span>
- </div>
-
- <div
- class="ml-auto d-flex align-items-center text-secondary"
- >
- <gl-icon-stub
- class="ml-1"
- name="comments"
- size="16"
- />
-
- <span
- aria-label="2 comments"
- class="ml-1"
- >
-
- 2
-
- </span>
- </div>
- </div>
-</router-link-stub>
-`;
-
-exports[`Design management list item component with notes renders item with single comment 1`] = `
-<router-link-stub
- class="card cursor-pointer text-plain js-design-list-item design-list-item"
- to="[object Object]"
->
- <div
- class="card-body p-0 d-flex-center overflow-hidden position-relative"
- >
- <!---->
-
- <gl-intersection-observer-stub>
- <!---->
-
- <img
- alt="test"
- class="block mx-auto mw-100 mh-100 design-img"
- data-qa-selector="design_image"
- src=""
- />
- </gl-intersection-observer-stub>
- </div>
-
- <div
- class="card-footer d-flex w-100"
- >
- <div
- class="d-flex flex-column str-truncated-100"
- >
- <span
- class="bold str-truncated-100"
- data-qa-selector="design_file_name"
- >
- test
- </span>
-
- <span
- class="str-truncated-100"
- >
-
- Updated
- <timeago-stub
- cssclass=""
- time="01-01-2019"
- tooltipplacement="bottom"
- />
- </span>
- </div>
-
- <div
- class="ml-auto d-flex align-items-center text-secondary"
- >
- <gl-icon-stub
- class="ml-1"
- name="comments"
- size="16"
- />
-
- <span
- aria-label="1 comment"
- class="ml-1"
- >
-
- 1
-
- </span>
- </div>
- </div>
-</router-link-stub>
-`;
diff --git a/spec/frontend/design_management_legacy/components/list/item_spec.js b/spec/frontend/design_management_legacy/components/list/item_spec.js
deleted file mode 100644
index 02e52e4b9d1..00000000000
--- a/spec/frontend/design_management_legacy/components/list/item_spec.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
-import VueRouter from 'vue-router';
-import Item from '~/design_management_legacy/components/list/item.vue';
-
-const localVue = createLocalVue();
-localVue.use(VueRouter);
-const router = new VueRouter();
-
-// Referenced from: doc/api/graphql/reference/gitlab_schema.graphql:DesignVersionEvent
-const DESIGN_VERSION_EVENT = {
- CREATION: 'CREATION',
- DELETION: 'DELETION',
- MODIFICATION: 'MODIFICATION',
- NO_CHANGE: 'NONE',
-};
-
-describe('Design management list item component', () => {
- let wrapper;
-
- const findDesignEvent = () => wrapper.find('[data-testid="designEvent"]');
- const findEventIcon = () => findDesignEvent().find(GlIcon);
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- function createComponent({
- notesCount = 0,
- event = DESIGN_VERSION_EVENT.NO_CHANGE,
- isUploading = false,
- imageLoading = false,
- } = {}) {
- wrapper = shallowMount(Item, {
- localVue,
- router,
- propsData: {
- id: 1,
- filename: 'test',
- image: 'http://via.placeholder.com/300',
- isUploading,
- event,
- notesCount,
- updatedAt: '01-01-2019',
- },
- data() {
- return {
- imageLoading,
- };
- },
- stubs: ['router-link'],
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when item is not in view', () => {
- it('image is not rendered', () => {
- createComponent();
-
- const image = wrapper.find('img');
- expect(image.attributes('src')).toBe('');
- });
- });
-
- describe('when item appears in view', () => {
- let image;
- let glIntersectionObserver;
-
- beforeEach(() => {
- createComponent();
- image = wrapper.find('img');
- glIntersectionObserver = wrapper.find(GlIntersectionObserver);
-
- glIntersectionObserver.vm.$emit('appear');
- return wrapper.vm.$nextTick();
- });
-
- describe('before image is loaded', () => {
- it('renders loading spinner', () => {
- expect(wrapper.find(GlLoadingIcon)).toExist();
- });
- });
-
- describe('after image is loaded', () => {
- beforeEach(() => {
- image.trigger('load');
- return wrapper.vm.$nextTick();
- });
-
- it('renders an image', () => {
- expect(image.attributes('src')).toBe('http://via.placeholder.com/300');
- expect(image.isVisible()).toBe(true);
- });
-
- it('renders media broken icon when image onerror triggered', () => {
- image.trigger('error');
- return wrapper.vm.$nextTick().then(() => {
- expect(image.isVisible()).toBe(false);
- expect(wrapper.find(GlIcon).element).toMatchSnapshot();
- });
- });
-
- describe('when imageV432x230 and image provided', () => {
- it('renders imageV432x230 image', () => {
- const mockSrc = 'mock-imageV432x230-url';
- wrapper.setProps({ imageV432x230: mockSrc });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(image.attributes('src')).toBe(mockSrc);
- });
- });
- });
-
- describe('when image disappears from view and then reappears', () => {
- beforeEach(() => {
- glIntersectionObserver.vm.$emit('appear');
- return wrapper.vm.$nextTick();
- });
-
- it('renders an image', () => {
- expect(image.isVisible()).toBe(true);
- });
- });
- });
- });
-
- describe('with notes', () => {
- it('renders item with single comment', () => {
- createComponent({ notesCount: 1 });
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders item with multiple comments', () => {
- createComponent({ notesCount: 2 });
-
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders loading spinner when isUploading is true', () => {
- createComponent({ isUploading: true });
-
- expect(findLoadingIcon().exists()).toBe(true);
- });
-
- it('renders item with no status icon for none event', () => {
- createComponent();
-
- expect(findDesignEvent().exists()).toBe(false);
- });
-
- describe('with associated event', () => {
- it.each`
- event | icon | className
- ${DESIGN_VERSION_EVENT.MODIFICATION} | ${'file-modified-solid'} | ${'text-primary-500'}
- ${DESIGN_VERSION_EVENT.DELETION} | ${'file-deletion-solid'} | ${'text-danger-500'}
- ${DESIGN_VERSION_EVENT.CREATION} | ${'file-addition-solid'} | ${'text-success-500'}
- `('renders item with correct status icon for $event event', ({ event, icon, className }) => {
- createComponent({ event });
- const eventIcon = findEventIcon();
-
- expect(eventIcon.exists()).toBe(true);
- expect(eventIcon.props('name')).toBe(icon);
- expect(eventIcon.classes()).toContain(className);
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/index_spec.js.snap
deleted file mode 100644
index 3d69821003a..00000000000
--- a/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/index_spec.js.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management toolbar component renders design and updated data 1`] = `
-<header
- class="d-flex p-2 bg-white align-items-center js-design-header"
->
- <a
- aria-label="Go back to designs"
- class="mr-3 text-plain d-flex justify-content-center align-items-center"
- >
- <gl-icon-stub
- name="close"
- size="18"
- />
- </a>
-
- <div
- class="overflow-hidden d-flex align-items-center"
- >
- <h2
- class="m-0 str-truncated-100 gl-font-base"
- >
- test.jpg
- </h2>
-
- <small
- class="text-secondary"
- >
- Updated 1 hour ago by Test Name
- </small>
- </div>
-
- <pagination-stub
- class="ml-auto flex-shrink-0"
- id="1"
- />
-
- <gl-deprecated-button-stub
- class="mr-2"
- href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
- size="md"
- variant="secondary"
- >
- <gl-icon-stub
- name="download"
- size="18"
- />
- </gl-deprecated-button-stub>
-
- <delete-button-stub
- buttonclass=""
- buttonvariant="danger"
- hasselecteddesigns="true"
- >
- <gl-icon-stub
- name="remove"
- size="18"
- />
- </delete-button-stub>
-</header>
-`;
diff --git a/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_button_spec.js.snap b/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_button_spec.js.snap
deleted file mode 100644
index 6bc24613140..00000000000
--- a/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_button_spec.js.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management pagination button component disables button when no design is passed 1`] = `
-<router-link-stub
- aria-label="Test title"
- class="btn btn-default disabled"
- disabled="true"
- to="[object Object]"
->
- <gl-icon-stub
- name="angle-right"
- size="16"
- />
-</router-link-stub>
-`;
-
-exports[`Design management pagination button component renders router-link 1`] = `
-<router-link-stub
- aria-label="Test title"
- class="btn btn-default"
- to="[object Object]"
->
- <gl-icon-stub
- name="angle-right"
- size="16"
- />
-</router-link-stub>
-`;
diff --git a/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_spec.js.snap b/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_spec.js.snap
deleted file mode 100644
index 0197b4bff79..00000000000
--- a/spec/frontend/design_management_legacy/components/toolbar/__snapshots__/pagination_spec.js.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management pagination component hides components when designs are empty 1`] = `<!---->`;
-
-exports[`Design management pagination component renders pagination buttons 1`] = `
-<div
- class="d-flex align-items-center"
->
-
- 0 of 2
-
- <div
- class="btn-group ml-3 mr-3"
- >
- <pagination-button-stub
- class="js-previous-design"
- iconname="angle-left"
- title="Go to previous design"
- />
-
- <pagination-button-stub
- class="js-next-design"
- design="[object Object]"
- iconname="angle-right"
- title="Go to next design"
- />
- </div>
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/components/toolbar/index_spec.js b/spec/frontend/design_management_legacy/components/toolbar/index_spec.js
deleted file mode 100644
index 8207cad4136..00000000000
--- a/spec/frontend/design_management_legacy/components/toolbar/index_spec.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import VueRouter from 'vue-router';
-import { GlDeprecatedButton } from '@gitlab/ui';
-import Toolbar from '~/design_management_legacy/components/toolbar/index.vue';
-import DeleteButton from '~/design_management_legacy/components/delete_button.vue';
-import { DESIGNS_ROUTE_NAME } from '~/design_management_legacy/router/constants';
-
-const localVue = createLocalVue();
-localVue.use(VueRouter);
-const router = new VueRouter();
-
-const RouterLinkStub = {
- props: {
- to: {
- type: Object,
- },
- },
- render(createElement) {
- return createElement('a', {}, this.$slots.default);
- },
-};
-
-describe('Design management toolbar component', () => {
- let wrapper;
-
- function createComponent(isLoading = false, createDesign = true, props) {
- const updatedAt = new Date();
- updatedAt.setHours(updatedAt.getHours() - 1);
-
- wrapper = shallowMount(Toolbar, {
- localVue,
- router,
- propsData: {
- id: '1',
- isLatestVersion: true,
- isLoading,
- isDeleting: false,
- filename: 'test.jpg',
- updatedAt: updatedAt.toString(),
- updatedBy: {
- name: 'Test Name',
- },
- image: '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
- ...props,
- },
- stubs: {
- 'router-link': RouterLinkStub,
- },
- });
-
- wrapper.setData({
- permissions: {
- createDesign,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders design and updated data', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('links back to designs list', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- const link = wrapper.find('a');
-
- expect(link.props('to')).toEqual({
- name: DESIGNS_ROUTE_NAME,
- query: {
- version: undefined,
- },
- });
- });
- });
-
- it('renders delete button on latest designs version with logged in user', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DeleteButton).exists()).toBe(true);
- });
- });
-
- it('does not render delete button on non-latest version', () => {
- createComponent(false, true, { isLatestVersion: false });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DeleteButton).exists()).toBe(false);
- });
- });
-
- it('does not render delete button when user is not logged in', () => {
- createComponent(false, false);
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DeleteButton).exists()).toBe(false);
- });
- });
-
- it('emits `delete` event on deleteButton `deleteSelectedDesigns` event', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- wrapper.find(DeleteButton).vm.$emit('deleteSelectedDesigns');
- expect(wrapper.emitted().delete).toBeTruthy();
- });
- });
-
- it('renders download button with correct link', () => {
- expect(wrapper.find(GlDeprecatedButton).attributes('href')).toBe(
- '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
- );
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/toolbar/pagination_button_spec.js b/spec/frontend/design_management_legacy/components/toolbar/pagination_button_spec.js
deleted file mode 100644
index d2153adca45..00000000000
--- a/spec/frontend/design_management_legacy/components/toolbar/pagination_button_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import VueRouter from 'vue-router';
-import PaginationButton from '~/design_management_legacy/components/toolbar/pagination_button.vue';
-import { DESIGN_ROUTE_NAME } from '~/design_management_legacy/router/constants';
-
-const localVue = createLocalVue();
-localVue.use(VueRouter);
-const router = new VueRouter();
-
-describe('Design management pagination button component', () => {
- let wrapper;
-
- function createComponent(design = null) {
- wrapper = shallowMount(PaginationButton, {
- localVue,
- router,
- propsData: {
- design,
- title: 'Test title',
- iconName: 'angle-right',
- },
- stubs: ['router-link'],
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('disables button when no design is passed', () => {
- createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders router-link', () => {
- createComponent({ id: '2' });
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('designLink', () => {
- it('returns empty link when design is null', () => {
- createComponent();
-
- expect(wrapper.vm.designLink).toEqual({});
- });
-
- it('returns design link', () => {
- createComponent({ id: '2', filename: 'test' });
-
- wrapper.vm.$router.replace('/root/test-project/issues/1/designs/test?version=1');
-
- expect(wrapper.vm.designLink).toEqual({
- name: DESIGN_ROUTE_NAME,
- params: { id: 'test' },
- query: { version: '1' },
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/toolbar/pagination_spec.js b/spec/frontend/design_management_legacy/components/toolbar/pagination_spec.js
deleted file mode 100644
index 21b55113a6e..00000000000
--- a/spec/frontend/design_management_legacy/components/toolbar/pagination_spec.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/* global Mousetrap */
-import 'mousetrap';
-import { shallowMount } from '@vue/test-utils';
-import Pagination from '~/design_management_legacy/components/toolbar/pagination.vue';
-import { DESIGN_ROUTE_NAME } from '~/design_management_legacy/router/constants';
-
-const push = jest.fn();
-const $router = {
- push,
-};
-
-const $route = {
- path: '/designs/design-2',
- query: {},
-};
-
-describe('Design management pagination component', () => {
- let wrapper;
-
- function createComponent() {
- wrapper = shallowMount(Pagination, {
- propsData: {
- id: '2',
- },
- mocks: {
- $router,
- $route,
- },
- });
- }
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('hides components when designs are empty', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders pagination buttons', () => {
- wrapper.setData({
- designs: [{ id: '1' }, { id: '2' }],
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- describe('keyboard buttons navigation', () => {
- beforeEach(() => {
- wrapper.setData({
- designs: [{ filename: '1' }, { filename: '2' }, { filename: '3' }],
- });
- });
-
- it('routes to previous design on Left button', () => {
- Mousetrap.trigger('left');
- expect(push).toHaveBeenCalledWith({
- name: DESIGN_ROUTE_NAME,
- params: { id: '1' },
- query: {},
- });
- });
-
- it('routes to next design on Right button', () => {
- Mousetrap.trigger('right');
- expect(push).toHaveBeenCalledWith({
- name: DESIGN_ROUTE_NAME,
- params: { id: '3' },
- query: {},
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/upload/__snapshots__/button_spec.js.snap b/spec/frontend/design_management_legacy/components/upload/__snapshots__/button_spec.js.snap
deleted file mode 100644
index 27c0ba589e6..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/__snapshots__/button_spec.js.snap
+++ /dev/null
@@ -1,79 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management upload button component renders inverted upload design button 1`] = `
-<div
- isinverted="true"
->
- <gl-deprecated-button-stub
- size="md"
- title="Adding a design with the same filename replaces the file in a new version."
- variant="success"
- >
-
- Upload designs
-
- <!---->
- </gl-deprecated-button-stub>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-</div>
-`;
-
-exports[`Design management upload button component renders loading icon 1`] = `
-<div>
- <gl-deprecated-button-stub
- disabled="true"
- size="md"
- title="Adding a design with the same filename replaces the file in a new version."
- variant="success"
- >
-
- Upload designs
-
- <gl-loading-icon-stub
- class="ml-1"
- color="orange"
- inline="true"
- label="Loading"
- size="sm"
- />
- </gl-deprecated-button-stub>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-</div>
-`;
-
-exports[`Design management upload button component renders upload design button 1`] = `
-<div>
- <gl-deprecated-button-stub
- size="md"
- title="Adding a design with the same filename replaces the file in a new version."
- variant="success"
- >
-
- Upload designs
-
- <!---->
- </gl-deprecated-button-stub>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/components/upload/__snapshots__/design_dropzone_spec.js.snap b/spec/frontend/design_management_legacy/components/upload/__snapshots__/design_dropzone_spec.js.snap
deleted file mode 100644
index 0737b9729a2..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/__snapshots__/design_dropzone_spec.js.snap
+++ /dev/null
@@ -1,455 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management dropzone component when dragging renders correct template when drag event contains files 1`] = `
-<div
- class="w-100 position-relative"
->
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- >
- <div
- class="d-flex-center flex-column text-center"
- >
- <gl-icon-stub
- class="mb-4"
- name="doc-new"
- size="48"
- />
-
- <p>
- <gl-sprintf-stub
- message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
- />
- </p>
- </div>
- </button>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style=""
- >
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style=""
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
-
-exports[`Design management dropzone component when dragging renders correct template when drag event contains files and text 1`] = `
-<div
- class="w-100 position-relative"
->
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- >
- <div
- class="d-flex-center flex-column text-center"
- >
- <gl-icon-stub
- class="mb-4"
- name="doc-new"
- size="48"
- />
-
- <p>
- <gl-sprintf-stub
- message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
- />
- </p>
- </div>
- </button>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style=""
- >
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style=""
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
-
-exports[`Design management dropzone component when dragging renders correct template when drag event contains text 1`] = `
-<div
- class="w-100 position-relative"
->
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- >
- <div
- class="d-flex-center flex-column text-center"
- >
- <gl-icon-stub
- class="mb-4"
- name="doc-new"
- size="48"
- />
-
- <p>
- <gl-sprintf-stub
- message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
- />
- </p>
- </div>
- </button>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style=""
- >
- <div
- class="mw-50 text-center"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
-
-exports[`Design management dropzone component when dragging renders correct template when drag event is empty 1`] = `
-<div
- class="w-100 position-relative"
->
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- >
- <div
- class="d-flex-center flex-column text-center"
- >
- <gl-icon-stub
- class="mb-4"
- name="doc-new"
- size="48"
- />
-
- <p>
- <gl-sprintf-stub
- message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
- />
- </p>
- </div>
- </button>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style=""
- >
- <div
- class="mw-50 text-center"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
-
-exports[`Design management dropzone component when dragging renders correct template when dragging stops 1`] = `
-<div
- class="w-100 position-relative"
->
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- >
- <div
- class="d-flex-center flex-column text-center"
- >
- <gl-icon-stub
- class="mb-4"
- name="doc-new"
- size="48"
- />
-
- <p>
- <gl-sprintf-stub
- message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
- />
- </p>
- </div>
- </button>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style="display: none;"
- >
- <div
- class="mw-50 text-center"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
-
-exports[`Design management dropzone component when no slot provided renders default dropzone card 1`] = `
-<div
- class="w-100 position-relative"
->
- <button
- class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
- >
- <div
- class="d-flex-center flex-column text-center"
- >
- <gl-icon-stub
- class="mb-4"
- name="doc-new"
- size="48"
- />
-
- <p>
- <gl-sprintf-stub
- message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
- />
- </p>
- </div>
- </button>
-
- <input
- accept="image/*"
- class="hide"
- multiple="multiple"
- name="design_file"
- type="file"
- />
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style="display: none;"
- >
- <div
- class="mw-50 text-center"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
-
-exports[`Design management dropzone component when slot provided renders dropzone with slot content 1`] = `
-<div
- class="w-100 position-relative"
->
- <div>
- dropzone slot
- </div>
-
- <transition-stub
- name="design-dropzone-fade"
- >
- <div
- class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
- style="display: none;"
- >
- <div
- class="mw-50 text-center"
- >
- <h3>
- Oh no!
- </h3>
-
- <span>
- You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
- </span>
- </div>
-
- <div
- class="mw-50 text-center"
- style="display: none;"
- >
- <h3>
- Incoming!
- </h3>
-
- <span>
- Drop your designs to start your upload.
- </span>
- </div>
- </div>
- </transition-stub>
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/components/upload/__snapshots__/design_version_dropdown_spec.js.snap b/spec/frontend/design_management_legacy/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
deleted file mode 100644
index d34b925f33d..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
+++ /dev/null
@@ -1,111 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
-<gl-deprecated-dropdown-stub
- class="design-version-dropdown"
- issueiid=""
- projectpath=""
- text="Showing Latest Version"
- variant="link"
->
- <gl-deprecated-dropdown-item-stub>
- <router-link-stub
- class="d-flex js-version-link"
- to="[object Object]"
- >
- <div
- class="flex-grow-1 ml-2"
- >
- <div>
- <strong>
- Version 2
-
- <span>
- (latest)
- </span>
- </strong>
- </div>
- </div>
-
- <i
- class="fa fa-check float-right gl-mr-2"
- />
- </router-link-stub>
- </gl-deprecated-dropdown-item-stub>
- <gl-deprecated-dropdown-item-stub>
- <router-link-stub
- class="d-flex js-version-link"
- to="[object Object]"
- >
- <div
- class="flex-grow-1 ml-2"
- >
- <div>
- <strong>
- Version 1
-
- <!---->
- </strong>
- </div>
- </div>
-
- <!---->
- </router-link-stub>
- </gl-deprecated-dropdown-item-stub>
-</gl-deprecated-dropdown-stub>
-`;
-
-exports[`Design management design version dropdown component renders design version list 1`] = `
-<gl-deprecated-dropdown-stub
- class="design-version-dropdown"
- issueiid=""
- projectpath=""
- text="Showing Latest Version"
- variant="link"
->
- <gl-deprecated-dropdown-item-stub>
- <router-link-stub
- class="d-flex js-version-link"
- to="[object Object]"
- >
- <div
- class="flex-grow-1 ml-2"
- >
- <div>
- <strong>
- Version 2
-
- <span>
- (latest)
- </span>
- </strong>
- </div>
- </div>
-
- <i
- class="fa fa-check float-right gl-mr-2"
- />
- </router-link-stub>
- </gl-deprecated-dropdown-item-stub>
- <gl-deprecated-dropdown-item-stub>
- <router-link-stub
- class="d-flex js-version-link"
- to="[object Object]"
- >
- <div
- class="flex-grow-1 ml-2"
- >
- <div>
- <strong>
- Version 1
-
- <!---->
- </strong>
- </div>
- </div>
-
- <!---->
- </router-link-stub>
- </gl-deprecated-dropdown-item-stub>
-</gl-deprecated-dropdown-stub>
-`;
diff --git a/spec/frontend/design_management_legacy/components/upload/button_spec.js b/spec/frontend/design_management_legacy/components/upload/button_spec.js
deleted file mode 100644
index dde5c694194..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/button_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import UploadButton from '~/design_management_legacy/components/upload/button.vue';
-
-describe('Design management upload button component', () => {
- let wrapper;
-
- function createComponent(isSaving = false, isInverted = false) {
- wrapper = shallowMount(UploadButton, {
- propsData: {
- isSaving,
- isInverted,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders upload design button', () => {
- createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders inverted upload design button', () => {
- createComponent(false, true);
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders loading icon', () => {
- createComponent(true);
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('onFileUploadChange', () => {
- it('emits upload event', () => {
- createComponent();
-
- wrapper.vm.onFileUploadChange({ target: { files: 'test' } });
-
- expect(wrapper.emitted().upload[0]).toEqual(['test']);
- });
- });
-
- describe('openFileUpload', () => {
- it('triggers click on input', () => {
- createComponent();
-
- const clickSpy = jest.spyOn(wrapper.find('input').element, 'click');
-
- wrapper.vm.openFileUpload();
-
- expect(clickSpy).toHaveBeenCalled();
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/upload/design_dropzone_spec.js b/spec/frontend/design_management_legacy/components/upload/design_dropzone_spec.js
deleted file mode 100644
index 1907a3124a6..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/design_dropzone_spec.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import DesignDropzone from '~/design_management_legacy/components/upload/design_dropzone.vue';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-
-jest.mock('~/flash');
-
-describe('Design management dropzone component', () => {
- let wrapper;
-
- const mockDragEvent = ({ types = ['Files'], files = [] }) => {
- return { dataTransfer: { types, files } };
- };
-
- const findDropzoneCard = () => wrapper.find('.design-dropzone-card');
-
- function createComponent({ slots = {}, data = {} } = {}) {
- wrapper = shallowMount(DesignDropzone, {
- slots,
- data() {
- return data;
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when slot provided', () => {
- it('renders dropzone with slot content', () => {
- createComponent({
- slots: {
- default: ['<div>dropzone slot</div>'],
- },
- });
-
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- describe('when no slot provided', () => {
- it('renders default dropzone card', () => {
- createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('triggers click event on file input element when clicked', () => {
- createComponent();
- const clickSpy = jest.spyOn(wrapper.find('input').element, 'click');
-
- findDropzoneCard().trigger('click');
- expect(clickSpy).toHaveBeenCalled();
- });
- });
-
- describe('when dragging', () => {
- it.each`
- description | eventPayload
- ${'is empty'} | ${{}}
- ${'contains text'} | ${mockDragEvent({ types: ['text'] })}
- ${'contains files and text'} | ${mockDragEvent({ types: ['Files', 'text'] })}
- ${'contains files'} | ${mockDragEvent({ types: ['Files'] })}
- `('renders correct template when drag event $description', ({ eventPayload }) => {
- createComponent();
-
- wrapper.trigger('dragenter', eventPayload);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders correct template when dragging stops', () => {
- createComponent();
-
- wrapper.trigger('dragenter');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.trigger('dragleave');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
- });
-
- describe('when dropping', () => {
- it('emits upload event', () => {
- createComponent();
- const mockFile = { name: 'test', type: 'image/jpg' };
- const mockEvent = mockDragEvent({ files: [mockFile] });
-
- wrapper.trigger('dragenter', mockEvent);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.trigger('drop', mockEvent);
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
- });
- });
- });
-
- describe('ondrop', () => {
- const mockData = { dragCounter: 1, isDragDataValid: true };
-
- describe('when drag data is valid', () => {
- it('emits upload event for valid files', () => {
- createComponent({ data: mockData });
-
- const mockFile = { type: 'image/jpg' };
- const mockEvent = mockDragEvent({ files: [mockFile] });
-
- wrapper.vm.ondrop(mockEvent);
- expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
- });
-
- it('calls createFlash when files are invalid', () => {
- createComponent({ data: mockData });
-
- const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] });
-
- wrapper.vm.ondrop(mockEvent);
- expect(createFlash).toHaveBeenCalledTimes(1);
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/upload/design_version_dropdown_spec.js b/spec/frontend/design_management_legacy/components/upload/design_version_dropdown_spec.js
deleted file mode 100644
index 7fb85f357c7..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/design_version_dropdown_spec.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
-import DesignVersionDropdown from '~/design_management_legacy/components/upload/design_version_dropdown.vue';
-import mockAllVersions from './mock_data/all_versions';
-
-const LATEST_VERSION_ID = 3;
-const PREVIOUS_VERSION_ID = 2;
-
-const designRouteFactory = versionId => ({
- path: `/designs?version=${versionId}`,
- query: {
- version: `${versionId}`,
- },
-});
-
-const MOCK_ROUTE = {
- path: '/designs',
- query: {},
-};
-
-describe('Design management design version dropdown component', () => {
- let wrapper;
-
- function createComponent({ maxVersions = -1, $route = MOCK_ROUTE } = {}) {
- wrapper = shallowMount(DesignVersionDropdown, {
- propsData: {
- projectPath: '',
- issueIid: '',
- },
- mocks: {
- $route,
- },
- stubs: ['router-link'],
- });
-
- wrapper.setData({
- allVersions: maxVersions > -1 ? mockAllVersions.slice(0, maxVersions) : mockAllVersions,
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findVersionLink = index => wrapper.findAll('.js-version-link').at(index);
-
- it('renders design version dropdown button', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders design version list', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- describe('selected version name', () => {
- it('has "latest" on most recent version item', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findVersionLink(0).text()).toContain('latest');
- });
- });
- });
-
- describe('versions list', () => {
- it('displays latest version text by default', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(
- 'Showing Latest Version',
- );
- });
- });
-
- it('displays latest version text when only 1 version is present', () => {
- createComponent({ maxVersions: 1 });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(
- 'Showing Latest Version',
- );
- });
- });
-
- it('displays version text when the current version is not the latest', () => {
- createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(`Showing Version #1`);
- });
- });
-
- it('displays latest version text when the current version is the latest', () => {
- createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(
- 'Showing Latest Version',
- );
- });
- });
-
- it('should have the same length as apollo query', () => {
- createComponent();
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlDeprecatedDropdownItem)).toHaveLength(
- wrapper.vm.allVersions.length,
- );
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/components/upload/mock_data/all_versions.js b/spec/frontend/design_management_legacy/components/upload/mock_data/all_versions.js
deleted file mode 100644
index e76bbd261bd..00000000000
--- a/spec/frontend/design_management_legacy/components/upload/mock_data/all_versions.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export default [
- {
- node: {
- id: 'gid://gitlab/DesignManagement::Version/3',
- sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
- },
- },
- {
- node: {
- id: 'gid://gitlab/DesignManagement::Version/2',
- sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
- },
- },
-];
diff --git a/spec/frontend/design_management_legacy/mock_data/all_versions.js b/spec/frontend/design_management_legacy/mock_data/all_versions.js
deleted file mode 100644
index c389fdb8747..00000000000
--- a/spec/frontend/design_management_legacy/mock_data/all_versions.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default [
- {
- node: {
- id: 'gid://gitlab/DesignManagement::Version/1',
- sha: 'b389071a06c153509e11da1f582005b316667001',
- },
- },
-];
diff --git a/spec/frontend/design_management_legacy/mock_data/design.js b/spec/frontend/design_management_legacy/mock_data/design.js
deleted file mode 100644
index 675198b9408..00000000000
--- a/spec/frontend/design_management_legacy/mock_data/design.js
+++ /dev/null
@@ -1,74 +0,0 @@
-export default {
- id: 'design-id',
- filename: 'test.jpg',
- fullPath: 'full-design-path',
- image: 'test.jpg',
- updatedAt: '01-01-2019',
- updatedBy: {
- name: 'test',
- },
- issue: {
- title: 'My precious issue',
- webPath: 'full-issue-path',
- webUrl: 'full-issue-url',
- participants: {
- edges: [
- {
- node: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'link-to-author',
- avatarUrl: 'link-to-avatar',
- },
- },
- ],
- },
- },
- discussions: {
- nodes: [
- {
- id: 'discussion-id',
- replyId: 'discussion-reply-id',
- resolved: false,
- notes: {
- nodes: [
- {
- id: 'note-id',
- body: '123',
- author: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'link-to-author',
- avatarUrl: 'link-to-avatar',
- },
- },
- ],
- },
- },
- {
- id: 'discussion-resolved',
- replyId: 'discussion-reply-resolved',
- resolved: true,
- notes: {
- nodes: [
- {
- id: 'note-resolved',
- body: '123',
- author: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'link-to-author',
- avatarUrl: 'link-to-avatar',
- },
- },
- ],
- },
- },
- ],
- },
- diffRefs: {
- headSha: 'headSha',
- baseSha: 'baseSha',
- startSha: 'startSha',
- },
-};
diff --git a/spec/frontend/design_management_legacy/mock_data/designs.js b/spec/frontend/design_management_legacy/mock_data/designs.js
deleted file mode 100644
index 07f5c1b7457..00000000000
--- a/spec/frontend/design_management_legacy/mock_data/designs.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import design from './design';
-
-export default {
- project: {
- issue: {
- designCollection: {
- designs: {
- edges: [
- {
- node: design,
- },
- ],
- },
- },
- },
- },
-};
diff --git a/spec/frontend/design_management_legacy/mock_data/no_designs.js b/spec/frontend/design_management_legacy/mock_data/no_designs.js
deleted file mode 100644
index 9db0ffcade2..00000000000
--- a/spec/frontend/design_management_legacy/mock_data/no_designs.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export default {
- project: {
- issue: {
- designCollection: {
- designs: {
- edges: [],
- },
- },
- },
- },
-};
diff --git a/spec/frontend/design_management_legacy/mock_data/notes.js b/spec/frontend/design_management_legacy/mock_data/notes.js
deleted file mode 100644
index 80cb3944786..00000000000
--- a/spec/frontend/design_management_legacy/mock_data/notes.js
+++ /dev/null
@@ -1,46 +0,0 @@
-export default [
- {
- id: 'note-id-1',
- index: 1,
- position: {
- height: 100,
- width: 100,
- x: 10,
- y: 15,
- },
- author: {
- name: 'John',
- webUrl: 'link-to-john-profile',
- },
- createdAt: '2020-05-08T07:10:45Z',
- userPermissions: {
- adminNote: true,
- },
- discussion: {
- id: 'discussion-id-1',
- },
- resolved: false,
- },
- {
- id: 'note-id-2',
- index: 2,
- position: {
- height: 50,
- width: 50,
- x: 25,
- y: 25,
- },
- author: {
- name: 'Mary',
- webUrl: 'link-to-mary-profile',
- },
- createdAt: '2020-05-08T07:10:45Z',
- userPermissions: {
- adminNote: true,
- },
- discussion: {
- id: 'discussion-id-2',
- },
- resolved: true,
- },
-];
diff --git a/spec/frontend/design_management_legacy/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_legacy/pages/__snapshots__/index_spec.js.snap
deleted file mode 100644
index 3ba63fd14f0..00000000000
--- a/spec/frontend/design_management_legacy/pages/__snapshots__/index_spec.js.snap
+++ /dev/null
@@ -1,263 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management index page designs does not render toolbar when there is no permission 1`] = `
-<div>
- <!---->
-
- <div
- class="mt-4"
- >
- <ol
- class="list-unstyled row"
- >
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub
- class="design-list-item"
- />
- </li>
-
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub>
- <design-stub
- event="NONE"
- filename="design-1-name"
- id="design-1"
- image="design-1-image"
- notescount="0"
- />
- </design-dropzone-stub>
-
- <!---->
- </li>
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub>
- <design-stub
- event="NONE"
- filename="design-2-name"
- id="design-2"
- image="design-2-image"
- notescount="1"
- />
- </design-dropzone-stub>
-
- <!---->
- </li>
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub>
- <design-stub
- event="NONE"
- filename="design-3-name"
- id="design-3"
- image="design-3-image"
- notescount="0"
- />
- </design-dropzone-stub>
-
- <!---->
- </li>
- </ol>
- </div>
-
- <router-view-stub
- name="default"
- />
-</div>
-`;
-
-exports[`Design management index page designs renders designs list and header with upload button 1`] = `
-<div>
- <header
- class="row-content-block border-top-0 p-2 d-flex"
- >
- <div
- class="d-flex justify-content-between align-items-center w-100"
- >
- <design-version-dropdown-stub />
-
- <div
- class="qa-selector-toolbar d-flex"
- >
- <gl-deprecated-button-stub
- class="mr-2 js-select-all"
- size="md"
- variant="link"
- >
- Select all
- </gl-deprecated-button-stub>
-
- <div>
- <delete-button-stub
- buttonclass="btn-danger btn-inverted mr-2"
- buttonvariant=""
- >
-
- Delete selected
-
- <!---->
- </delete-button-stub>
- </div>
-
- <upload-button-stub />
- </div>
- </div>
- </header>
-
- <div
- class="mt-4"
- >
- <ol
- class="list-unstyled row"
- >
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub
- class="design-list-item"
- />
- </li>
-
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub>
- <design-stub
- event="NONE"
- filename="design-1-name"
- id="design-1"
- image="design-1-image"
- notescount="0"
- />
- </design-dropzone-stub>
-
- <input
- class="design-checkbox"
- type="checkbox"
- />
- </li>
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub>
- <design-stub
- event="NONE"
- filename="design-2-name"
- id="design-2"
- image="design-2-image"
- notescount="1"
- />
- </design-dropzone-stub>
-
- <input
- class="design-checkbox"
- type="checkbox"
- />
- </li>
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub>
- <design-stub
- event="NONE"
- filename="design-3-name"
- id="design-3"
- image="design-3-image"
- notescount="0"
- />
- </design-dropzone-stub>
-
- <input
- class="design-checkbox"
- type="checkbox"
- />
- </li>
- </ol>
- </div>
-
- <router-view-stub
- name="default"
- />
-</div>
-`;
-
-exports[`Design management index page designs renders error 1`] = `
-<div>
- <!---->
-
- <div
- class="mt-4"
- >
- <gl-alert-stub
- dismisslabel="Dismiss"
- primarybuttonlink=""
- primarybuttontext=""
- secondarybuttonlink=""
- secondarybuttontext=""
- title=""
- variant="danger"
- >
-
- An error occurred while loading designs. Please try again.
-
- </gl-alert-stub>
- </div>
-
- <router-view-stub
- name="default"
- />
-</div>
-`;
-
-exports[`Design management index page designs renders loading icon 1`] = `
-<div>
- <!---->
-
- <div
- class="mt-4"
- >
- <gl-loading-icon-stub
- color="orange"
- label="Loading"
- size="md"
- />
- </div>
-
- <router-view-stub
- name="default"
- />
-</div>
-`;
-
-exports[`Design management index page when has no designs renders empty text 1`] = `
-<div>
- <!---->
-
- <div
- class="mt-4"
- >
- <ol
- class="list-unstyled row"
- >
- <li
- class="col-md-6 col-lg-4 mb-3"
- >
- <design-dropzone-stub
- class="design-list-item"
- />
- </li>
-
- </ol>
- </div>
-
- <router-view-stub
- name="default"
- />
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap
deleted file mode 100644
index dc5baf37fc6..00000000000
--- a/spec/frontend/design_management_legacy/pages/design/__snapshots__/index_spec.js.snap
+++ /dev/null
@@ -1,216 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Design management design index page renders design index 1`] = `
-<div
- class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
->
- <div
- class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"
- >
- <design-destroyer-stub
- filenames="test.jpg"
- iid="1"
- projectpath=""
- />
-
- <!---->
-
- <design-presentation-stub
- discussions="[object Object],[object Object]"
- image="test.jpg"
- imagename="test.jpg"
- scale="1"
- />
-
- <div
- class="design-scaler-wrapper position-absolute mb-4 d-flex-center"
- >
- <design-scaler-stub />
- </div>
- </div>
-
- <div
- class="image-notes"
- >
- <h2
- class="gl-font-weight-bold gl-mt-0"
- >
-
- My precious issue
-
- </h2>
-
- <a
- class="gl-text-gray-400 gl-text-decoration-none gl-mb-6 gl-display-block"
- href="full-issue-url"
- >
- ull-issue-path
- </a>
-
- <participants-stub
- class="gl-mb-4"
- numberoflessparticipants="7"
- participants="[object Object]"
- />
-
- <!---->
-
- <design-discussion-stub
- data-testid="unresolved-discussion"
- designid="test"
- discussion="[object Object]"
- discussionwithopenform=""
- markdownpreviewpath="//preview_markdown?target_type=Issue"
- noteableid="design-id"
- />
-
- <gl-button-stub
- category="primary"
- class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4"
- data-testid="resolved-comments"
- icon="chevron-right"
- id="resolved-comments"
- size="medium"
- variant="link"
- >
- Resolved Comments (1)
-
- </gl-button-stub>
-
- <gl-popover-stub
- container="popovercontainer"
- cssclasses=""
- placement="top"
- show="true"
- target="resolved-comments"
- title="Resolved Comments"
- >
- <p>
-
- Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below
-
- </p>
-
- <a
- href="#"
- rel="noopener noreferrer"
- target="_blank"
- >
- Learn more about resolving comments
- </a>
- </gl-popover-stub>
-
- <gl-collapse-stub
- class="gl-mt-3"
- >
- <design-discussion-stub
- data-testid="resolved-discussion"
- designid="test"
- discussion="[object Object]"
- discussionwithopenform=""
- markdownpreviewpath="//preview_markdown?target_type=Issue"
- noteableid="design-id"
- />
- </gl-collapse-stub>
-
- </div>
-</div>
-`;
-
-exports[`Design management design index page sets loading state 1`] = `
-<div
- class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
->
- <gl-loading-icon-stub
- class="align-self-center"
- color="orange"
- label="Loading"
- size="xl"
- />
-</div>
-`;
-
-exports[`Design management design index page with error GlAlert is rendered in correct position with correct content 1`] = `
-<div
- class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
->
- <div
- class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"
- >
- <design-destroyer-stub
- filenames="test.jpg"
- iid="1"
- projectpath=""
- />
-
- <div
- class="p-3"
- >
- <gl-alert-stub
- dismissible="true"
- dismisslabel="Dismiss"
- primarybuttonlink=""
- primarybuttontext=""
- secondarybuttonlink=""
- secondarybuttontext=""
- title=""
- variant="danger"
- >
-
- woops
-
- </gl-alert-stub>
- </div>
-
- <design-presentation-stub
- discussions=""
- image="test.jpg"
- imagename="test.jpg"
- scale="1"
- />
-
- <div
- class="design-scaler-wrapper position-absolute mb-4 d-flex-center"
- >
- <design-scaler-stub />
- </div>
- </div>
-
- <div
- class="image-notes"
- >
- <h2
- class="gl-font-weight-bold gl-mt-0"
- >
-
- My precious issue
-
- </h2>
-
- <a
- class="gl-text-gray-400 gl-text-decoration-none gl-mb-6 gl-display-block"
- href="full-issue-url"
- >
- ull-issue-path
- </a>
-
- <participants-stub
- class="gl-mb-4"
- numberoflessparticipants="7"
- participants="[object Object]"
- />
-
- <h2
- class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
- data-testid="new-discussion-disclaimer"
- >
-
- Click the image where you'd like to start a new discussion
-
- </h2>
-
- <!---->
-
- </div>
-</div>
-`;
diff --git a/spec/frontend/design_management_legacy/pages/design/index_spec.js b/spec/frontend/design_management_legacy/pages/design/index_spec.js
deleted file mode 100644
index 5eb4158c715..00000000000
--- a/spec/frontend/design_management_legacy/pages/design/index_spec.js
+++ /dev/null
@@ -1,291 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import VueRouter from 'vue-router';
-import { GlAlert } from '@gitlab/ui';
-import { ApolloMutation } from 'vue-apollo';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import DesignIndex from '~/design_management_legacy/pages/design/index.vue';
-import DesignSidebar from '~/design_management_legacy/components/design_sidebar.vue';
-import DesignPresentation from '~/design_management_legacy/components/design_presentation.vue';
-import createImageDiffNoteMutation from '~/design_management_legacy/graphql/mutations/create_image_diff_note.mutation.graphql';
-import design from '../../mock_data/design';
-import mockResponseWithDesigns from '../../mock_data/designs';
-import mockResponseNoDesigns from '../../mock_data/no_designs';
-import mockAllVersions from '../../mock_data/all_versions';
-import {
- DESIGN_NOT_FOUND_ERROR,
- DESIGN_VERSION_NOT_EXIST_ERROR,
-} from '~/design_management_legacy/utils/error_messages';
-import { DESIGNS_ROUTE_NAME } from '~/design_management_legacy/router/constants';
-import createRouter from '~/design_management_legacy/router';
-import * as utils from '~/design_management_legacy/utils/design_management_utils';
-import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management_legacy/constants';
-
-jest.mock('~/flash');
-jest.mock('mousetrap', () => ({
- bind: jest.fn(),
- unbind: jest.fn(),
-}));
-
-const focusInput = jest.fn();
-
-const DesignReplyForm = {
- template: '<div><textarea ref="textarea"></textarea></div>',
- methods: {
- focusInput,
- },
-};
-
-const localVue = createLocalVue();
-localVue.use(VueRouter);
-
-describe('Design management design index page', () => {
- let wrapper;
- let router;
-
- const newComment = 'new comment';
- const annotationCoordinates = {
- x: 10,
- y: 10,
- width: 100,
- height: 100,
- };
- const createDiscussionMutationVariables = {
- mutation: createImageDiffNoteMutation,
- update: expect.anything(),
- variables: {
- input: {
- body: newComment,
- noteableId: design.id,
- position: {
- headSha: 'headSha',
- baseSha: 'baseSha',
- startSha: 'startSha',
- paths: {
- newPath: 'full-design-path',
- },
- ...annotationCoordinates,
- },
- },
- },
- };
-
- const mutate = jest.fn().mockResolvedValue();
-
- const findDiscussionForm = () => wrapper.find(DesignReplyForm);
- const findSidebar = () => wrapper.find(DesignSidebar);
- const findDesignPresentation = () => wrapper.find(DesignPresentation);
-
- function createComponent(loading = false, data = {}) {
- const $apollo = {
- queries: {
- design: {
- loading,
- },
- },
- mutate,
- };
-
- router = createRouter();
-
- wrapper = shallowMount(DesignIndex, {
- propsData: { id: '1' },
- mocks: { $apollo },
- stubs: {
- ApolloMutation,
- DesignSidebar,
- DesignReplyForm,
- },
- data() {
- return {
- issueIid: '1',
- activeDiscussion: {
- id: null,
- source: null,
- },
- ...data,
- };
- },
- localVue,
- router,
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when navigating', () => {
- it('applies fullscreen layout', () => {
- const mockEl = {
- classList: {
- add: jest.fn(),
- remove: jest.fn(),
- },
- };
- jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockEl);
- createComponent(true);
-
- wrapper.vm.$router.push('/designs/test');
- expect(mockEl.classList.add).toHaveBeenCalledTimes(1);
- expect(mockEl.classList.add).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
- });
- });
-
- it('sets loading state', () => {
- createComponent(true);
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('renders design index', () => {
- createComponent(false, { design });
-
- expect(wrapper.element).toMatchSnapshot();
- expect(wrapper.find(GlAlert).exists()).toBe(false);
- });
-
- it('passes correct props to sidebar component', () => {
- createComponent(false, { design });
-
- expect(findSidebar().props()).toEqual({
- design,
- markdownPreviewPath: '//preview_markdown?target_type=Issue',
- resolvedDiscussionsExpanded: false,
- });
- });
-
- it('opens a new discussion form', () => {
- createComponent(false, {
- design: {
- ...design,
- discussions: {
- nodes: [],
- },
- },
- });
-
- findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findDiscussionForm().exists()).toBe(true);
- });
- });
-
- it('keeps new discussion form focused', () => {
- createComponent(false, {
- design: {
- ...design,
- discussions: {
- nodes: [],
- },
- },
- annotationCoordinates,
- });
-
- findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 });
-
- expect(focusInput).toHaveBeenCalled();
- });
-
- it('sends a mutation on submitting form and closes form', () => {
- createComponent(false, {
- design: {
- ...design,
- discussions: {
- nodes: [],
- },
- },
- annotationCoordinates,
- comment: newComment,
- });
-
- findDiscussionForm().vm.$emit('submitForm');
- expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
-
- return wrapper.vm
- .$nextTick()
- .then(() => {
- return mutate({ variables: createDiscussionMutationVariables });
- })
- .then(() => {
- expect(findDiscussionForm().exists()).toBe(false);
- });
- });
-
- it('closes the form and clears the comment on canceling form', () => {
- createComponent(false, {
- design: {
- ...design,
- discussions: {
- nodes: [],
- },
- },
- annotationCoordinates,
- comment: newComment,
- });
-
- findDiscussionForm().vm.$emit('cancelForm');
-
- expect(wrapper.vm.comment).toBe('');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findDiscussionForm().exists()).toBe(false);
- });
- });
-
- describe('with error', () => {
- beforeEach(() => {
- createComponent(false, {
- design: {
- ...design,
- discussions: {
- nodes: [],
- },
- },
- errorMessage: 'woops',
- });
- });
-
- it('GlAlert is rendered in correct position with correct content', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- describe('onDesignQueryResult', () => {
- describe('with no designs', () => {
- it('redirects to /designs', () => {
- createComponent(true);
- router.push = jest.fn();
-
- wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(DESIGN_NOT_FOUND_ERROR);
- expect(router.push).toHaveBeenCalledTimes(1);
- expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
- });
- });
- });
-
- describe('when no design exists for given version', () => {
- it('redirects to /designs', () => {
- createComponent(true);
- wrapper.setData({
- allVersions: mockAllVersions,
- });
-
- // attempt to query for a version of the design that doesn't exist
- router.push({ query: { version: '999' } });
- router.push = jest.fn();
-
- wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(DESIGN_VERSION_NOT_EXIST_ERROR);
- expect(router.push).toHaveBeenCalledTimes(1);
- expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
- });
- });
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/pages/index_spec.js b/spec/frontend/design_management_legacy/pages/index_spec.js
deleted file mode 100644
index fed1f986bad..00000000000
--- a/spec/frontend/design_management_legacy/pages/index_spec.js
+++ /dev/null
@@ -1,536 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { ApolloMutation } from 'vue-apollo';
-import VueRouter from 'vue-router';
-import { GlEmptyState } from '@gitlab/ui';
-import Index from '~/design_management_legacy/pages/index.vue';
-import uploadDesignQuery from '~/design_management_legacy/graphql/mutations/upload_design.mutation.graphql';
-import DesignDestroyer from '~/design_management_legacy/components/design_destroyer.vue';
-import DesignDropzone from '~/design_management_legacy/components/upload/design_dropzone.vue';
-import DeleteButton from '~/design_management_legacy/components/delete_button.vue';
-import { DESIGNS_ROUTE_NAME } from '~/design_management_legacy/router/constants';
-import {
- EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
- EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
-} from '~/design_management_legacy/utils/error_messages';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import createRouter from '~/design_management_legacy/router';
-import * as utils from '~/design_management_legacy/utils/design_management_utils';
-import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management_legacy/constants';
-
-jest.mock('~/flash.js');
-const mockPageEl = {
- classList: {
- remove: jest.fn(),
- },
-};
-jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageEl);
-
-const localVue = createLocalVue();
-const router = createRouter();
-localVue.use(VueRouter);
-
-const mockDesigns = [
- {
- id: 'design-1',
- image: 'design-1-image',
- filename: 'design-1-name',
- event: 'NONE',
- notesCount: 0,
- },
- {
- id: 'design-2',
- image: 'design-2-image',
- filename: 'design-2-name',
- event: 'NONE',
- notesCount: 1,
- },
- {
- id: 'design-3',
- image: 'design-3-image',
- filename: 'design-3-name',
- event: 'NONE',
- notesCount: 0,
- },
-];
-
-const mockVersion = {
- node: {
- id: 'gid://gitlab/DesignManagement::Version/1',
- },
-};
-
-describe('Design management index page', () => {
- let mutate;
- let wrapper;
-
- const findDesignCheckboxes = () => wrapper.findAll('.design-checkbox');
- const findSelectAllButton = () => wrapper.find('.js-select-all');
- const findToolbar = () => wrapper.find('.qa-selector-toolbar');
- const findDeleteButton = () => wrapper.find(DeleteButton);
- const findDropzone = () => wrapper.findAll(DesignDropzone).at(0);
- const findFirstDropzoneWithDesign = () => wrapper.findAll(DesignDropzone).at(1);
-
- function createComponent({
- loading = false,
- designs = [],
- allVersions = [],
- createDesign = true,
- stubs = {},
- mockMutate = jest.fn().mockResolvedValue(),
- } = {}) {
- mutate = mockMutate;
- const $apollo = {
- queries: {
- designs: {
- loading,
- },
- permissions: {
- loading,
- },
- },
- mutate,
- };
-
- wrapper = shallowMount(Index, {
- mocks: { $apollo },
- localVue,
- router,
- stubs: { DesignDestroyer, ApolloMutation, ...stubs },
- attachToDocument: true,
- });
-
- wrapper.setData({
- designs,
- allVersions,
- issueIid: '1',
- permissions: {
- createDesign,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('designs', () => {
- it('renders loading icon', () => {
- createComponent({ loading: true });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders error', () => {
- createComponent();
-
- wrapper.setData({ error: true });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('renders a toolbar with buttons when there are designs', () => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findToolbar().exists()).toBe(true);
- });
- });
-
- it('renders designs list and header with upload button', () => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- it('does not render toolbar when there is no permission', () => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
- });
-
- describe('when has no designs', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders empty text', () =>
- wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- }));
-
- it('does not render a toolbar with buttons', () =>
- wrapper.vm.$nextTick().then(() => {
- expect(findToolbar().exists()).toBe(false);
- }));
- });
-
- describe('uploading designs', () => {
- it('calls mutation on upload', () => {
- createComponent({ stubs: { GlEmptyState } });
-
- const mutationVariables = {
- update: expect.anything(),
- context: {
- hasUpload: true,
- },
- mutation: uploadDesignQuery,
- variables: {
- files: [{ name: 'test' }],
- projectPath: '',
- iid: '1',
- },
- optimisticResponse: {
- __typename: 'Mutation',
- designManagementUpload: {
- __typename: 'DesignManagementUploadPayload',
- designs: [
- {
- __typename: 'Design',
- id: expect.anything(),
- image: '',
- imageV432x230: '',
- filename: 'test',
- fullPath: '',
- event: 'NONE',
- notesCount: 0,
- diffRefs: {
- __typename: 'DiffRefs',
- baseSha: '',
- startSha: '',
- headSha: '',
- },
- discussions: {
- __typename: 'DesignDiscussion',
- nodes: [],
- },
- versions: {
- __typename: 'DesignVersionConnection',
- edges: {
- __typename: 'DesignVersionEdge',
- node: {
- __typename: 'DesignVersion',
- id: expect.anything(),
- sha: expect.anything(),
- },
- },
- },
- },
- ],
- skippedDesigns: [],
- errors: [],
- },
- },
- };
-
- return wrapper.vm.$nextTick().then(() => {
- findDropzone().vm.$emit('change', [{ name: 'test' }]);
- expect(mutate).toHaveBeenCalledWith(mutationVariables);
- expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
- expect(wrapper.vm.isSaving).toBeTruthy();
- });
- });
-
- it('sets isSaving', () => {
- createComponent();
-
- const uploadDesign = wrapper.vm.onUploadDesign([
- {
- name: 'test',
- },
- ]);
-
- expect(wrapper.vm.isSaving).toBe(true);
-
- return uploadDesign.then(() => {
- expect(wrapper.vm.isSaving).toBe(false);
- });
- });
-
- it('updates state appropriately after upload complete', () => {
- createComponent({ stubs: { GlEmptyState } });
- wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
-
- wrapper.vm.onUploadDesignDone();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.filesToBeSaved).toEqual([]);
- expect(wrapper.vm.isSaving).toBeFalsy();
- expect(wrapper.vm.isLatestVersion).toBe(true);
- });
- });
-
- it('updates state appropriately after upload error', () => {
- createComponent({ stubs: { GlEmptyState } });
- wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
-
- wrapper.vm.onUploadDesignError();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.filesToBeSaved).toEqual([]);
- expect(wrapper.vm.isSaving).toBeFalsy();
- expect(createFlash).toHaveBeenCalled();
-
- createFlash.mockReset();
- });
- });
-
- it('does not call mutation if createDesign is false', () => {
- createComponent({ createDesign: false });
-
- wrapper.vm.onUploadDesign([]);
-
- expect(mutate).not.toHaveBeenCalled();
- });
-
- describe('upload count limit', () => {
- const MAXIMUM_FILE_UPLOAD_LIMIT = 10;
-
- afterEach(() => {
- createFlash.mockReset();
- });
-
- it('does not warn when the max files are uploaded', () => {
- createComponent();
-
- wrapper.vm.onUploadDesign(new Array(MAXIMUM_FILE_UPLOAD_LIMIT).fill(mockDesigns[0]));
-
- expect(createFlash).not.toHaveBeenCalled();
- });
-
- it('warns when too many files are uploaded', () => {
- createComponent();
-
- wrapper.vm.onUploadDesign(new Array(MAXIMUM_FILE_UPLOAD_LIMIT + 1).fill(mockDesigns[0]));
-
- expect(createFlash).toHaveBeenCalled();
- });
- });
-
- it('flashes warning if designs are skipped', () => {
- createComponent({
- mockMutate: () =>
- Promise.resolve({
- data: { designManagementUpload: { skippedDesigns: [{ filename: 'test.jpg' }] } },
- }),
- });
-
- const uploadDesign = wrapper.vm.onUploadDesign([
- {
- name: 'test',
- },
- ]);
-
- return uploadDesign.then(() => {
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(
- 'Upload skipped. test.jpg did not change.',
- 'warning',
- );
- });
- });
-
- describe('dragging onto an existing design', () => {
- beforeEach(() => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
- });
-
- it('calls onUploadDesign with valid upload', () => {
- wrapper.setMethods({
- onUploadDesign: jest.fn(),
- });
-
- const mockUploadPayload = [
- {
- name: mockDesigns[0].filename,
- },
- ];
-
- const designDropzone = findFirstDropzoneWithDesign();
- designDropzone.vm.$emit('change', mockUploadPayload);
-
- expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith(mockUploadPayload);
- });
-
- it.each`
- description | eventPayload | message
- ${'> 1 file'} | ${[{ name: 'test' }, { name: 'test-2' }]} | ${EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE}
- ${'different filename'} | ${[{ name: 'wrong-name' }]} | ${EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE}
- `('calls createFlash when upload has $description', ({ eventPayload, message }) => {
- const designDropzone = findFirstDropzoneWithDesign();
- designDropzone.vm.$emit('change', eventPayload);
-
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(message);
- });
- });
- });
-
- describe('on latest version when has designs', () => {
- beforeEach(() => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
- });
-
- it('renders design checkboxes', () => {
- expect(findDesignCheckboxes()).toHaveLength(mockDesigns.length);
- });
-
- it('renders toolbar buttons', () => {
- expect(findToolbar().exists()).toBe(true);
- expect(findToolbar().classes()).toContain('d-flex');
- expect(findToolbar().classes()).not.toContain('d-none');
- });
-
- it('adds two designs to selected designs when their checkboxes are checked', () => {
- findDesignCheckboxes()
- .at(0)
- .trigger('click');
-
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findDesignCheckboxes()
- .at(1)
- .trigger('click');
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findDeleteButton().exists()).toBe(true);
- expect(findSelectAllButton().text()).toBe('Deselect all');
- findDeleteButton().vm.$emit('deleteSelectedDesigns');
- const [{ variables }] = mutate.mock.calls[0];
- expect(variables.filenames).toStrictEqual([
- mockDesigns[0].filename,
- mockDesigns[1].filename,
- ]);
- });
- });
-
- it('adds all designs to selected designs when Select All button is clicked', () => {
- findSelectAllButton().vm.$emit('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findDeleteButton().props().hasSelectedDesigns).toBe(true);
- expect(findSelectAllButton().text()).toBe('Deselect all');
- expect(wrapper.vm.selectedDesigns).toEqual(mockDesigns.map(design => design.filename));
- });
- });
-
- it('removes all designs from selected designs when at least one design was selected', () => {
- findDesignCheckboxes()
- .at(0)
- .trigger('click');
-
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findSelectAllButton().vm.$emit('click');
- })
- .then(() => {
- expect(findDeleteButton().props().hasSelectedDesigns).toBe(false);
- expect(findSelectAllButton().text()).toBe('Select all');
- expect(wrapper.vm.selectedDesigns).toEqual([]);
- });
- });
- });
-
- it('on latest version when has no designs does not render toolbar buttons', () => {
- createComponent({ designs: [], allVersions: [mockVersion] });
- expect(findToolbar().exists()).toBe(false);
- });
-
- describe('on non-latest version', () => {
- beforeEach(() => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
- });
-
- it('does not render design checkboxes', async () => {
- await router.replace({
- name: DESIGNS_ROUTE_NAME,
- query: {
- version: '2',
- },
- });
- expect(findDesignCheckboxes()).toHaveLength(0);
- });
-
- it('does not render Delete selected button', () => {
- expect(findDeleteButton().exists()).toBe(false);
- });
-
- it('does not render Select All button', () => {
- expect(findSelectAllButton().exists()).toBe(false);
- });
- });
-
- describe('pasting a design', () => {
- let event;
- beforeEach(() => {
- createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
-
- wrapper.setMethods({
- onUploadDesign: jest.fn(),
- });
-
- event = new Event('paste');
- });
-
- it('calls onUploadDesign with valid paste', async () => {
- event.clipboardData = {
- files: [{ name: 'image.png', type: 'image/png' }],
- getData: () => 'test.png',
- };
-
- document.dispatchEvent(event);
-
- expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith([
- new File([{ name: 'image.png' }], 'test.png'),
- ]);
- });
-
- it('renames a design if it has an image.png filename', () => {
- event.clipboardData = {
- files: [{ name: 'image.png', type: 'image/png' }],
- getData: () => 'image.png',
- };
-
- document.dispatchEvent(event);
-
- expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith([
- new File([{ name: 'image.png' }], `design_${Date.now()}.png`),
- ]);
- });
-
- it('does not call onUploadDesign with invalid paste', () => {
- event.clipboardData = {
- items: [{ type: 'text/plain' }, { type: 'text' }],
- files: [],
- };
-
- document.dispatchEvent(event);
-
- expect(wrapper.vm.onUploadDesign).not.toHaveBeenCalled();
- });
- });
-
- describe('when navigating', () => {
- it('ensures fullscreen layout is not applied', async () => {
- createComponent(true);
-
- await wrapper.vm.$router.replace('/');
- await wrapper.vm.$router.replace('/designs');
- expect(mockPageEl.classList.remove).toHaveBeenCalledTimes(2);
- expect(mockPageEl.classList.remove).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/router_spec.js b/spec/frontend/design_management_legacy/router_spec.js
deleted file mode 100644
index 5f62793a243..00000000000
--- a/spec/frontend/design_management_legacy/router_spec.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { mount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import VueRouter from 'vue-router';
-import App from '~/design_management_legacy/components/app.vue';
-import Designs from '~/design_management_legacy/pages/index.vue';
-import DesignDetail from '~/design_management_legacy/pages/design/index.vue';
-import createRouter from '~/design_management_legacy/router';
-import {
- ROOT_ROUTE_NAME,
- DESIGNS_ROUTE_NAME,
- DESIGN_ROUTE_NAME,
-} from '~/design_management_legacy/router/constants';
-import '~/commons/bootstrap';
-
-function factory(routeArg) {
- const localVue = createLocalVue();
- localVue.use(VueRouter);
-
- window.gon = { sprite_icons: '' };
-
- const router = createRouter('/');
- if (routeArg !== undefined) {
- router.push(routeArg);
- }
-
- return mount(App, {
- localVue,
- router,
- mocks: {
- $apollo: {
- queries: {
- designs: { loading: true },
- design: { loading: true },
- permissions: { loading: true },
- },
- mutate: jest.fn(),
- },
- },
- });
-}
-
-jest.mock('mousetrap', () => ({
- bind: jest.fn(),
- unbind: jest.fn(),
-}));
-
-describe('Design management router', () => {
- afterEach(() => {
- window.location.hash = '';
- });
-
- describe.each([['/'], [{ name: ROOT_ROUTE_NAME }]])('root route', routeArg => {
- it('pushes home component', () => {
- const wrapper = factory(routeArg);
-
- expect(wrapper.find(Designs).exists()).toBe(true);
- });
- });
-
- describe.each([['/designs'], [{ name: DESIGNS_ROUTE_NAME }]])('designs route', routeArg => {
- it('pushes designs root component', () => {
- const wrapper = factory(routeArg);
-
- expect(wrapper.find(Designs).exists()).toBe(true);
- });
- });
-
- describe.each([['/designs/1'], [{ name: DESIGN_ROUTE_NAME, params: { id: '1' } }]])(
- 'designs detail route',
- routeArg => {
- it('pushes designs detail component', () => {
- const wrapper = factory(routeArg);
-
- return nextTick().then(() => {
- const detail = wrapper.find(DesignDetail);
- expect(detail.exists()).toBe(true);
- expect(detail.props('id')).toEqual('1');
- });
- });
- },
- );
-});
diff --git a/spec/frontend/design_management_legacy/utils/cache_update_spec.js b/spec/frontend/design_management_legacy/utils/cache_update_spec.js
deleted file mode 100644
index dce91b5e59b..00000000000
--- a/spec/frontend/design_management_legacy/utils/cache_update_spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { InMemoryCache } from 'apollo-cache-inmemory';
-import {
- updateStoreAfterDesignsDelete,
- updateStoreAfterAddDiscussionComment,
- updateStoreAfterAddImageDiffNote,
- updateStoreAfterUploadDesign,
- updateStoreAfterUpdateImageDiffNote,
-} from '~/design_management_legacy/utils/cache_update';
-import {
- designDeletionError,
- ADD_DISCUSSION_COMMENT_ERROR,
- ADD_IMAGE_DIFF_NOTE_ERROR,
- UPDATE_IMAGE_DIFF_NOTE_ERROR,
-} from '~/design_management_legacy/utils/error_messages';
-import design from '../mock_data/design';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-
-jest.mock('~/flash.js');
-
-describe('Design Management cache update', () => {
- const mockErrors = ['code red!'];
-
- let mockStore;
-
- beforeEach(() => {
- mockStore = new InMemoryCache();
- });
-
- describe('error handling', () => {
- it.each`
- fnName | subject | errorMessage | extraArgs
- ${'updateStoreAfterDesignsDelete'} | ${updateStoreAfterDesignsDelete} | ${designDeletionError({ singular: true })} | ${[[design]]}
- ${'updateStoreAfterAddDiscussionComment'} | ${updateStoreAfterAddDiscussionComment} | ${ADD_DISCUSSION_COMMENT_ERROR} | ${[]}
- ${'updateStoreAfterAddImageDiffNote'} | ${updateStoreAfterAddImageDiffNote} | ${ADD_IMAGE_DIFF_NOTE_ERROR} | ${[]}
- ${'updateStoreAfterUploadDesign'} | ${updateStoreAfterUploadDesign} | ${mockErrors[0]} | ${[]}
- ${'updateStoreAfterUpdateImageDiffNote'} | ${updateStoreAfterUpdateImageDiffNote} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR} | ${[]}
- `('$fnName handles errors in response', ({ subject, extraArgs, errorMessage }) => {
- expect(createFlash).not.toHaveBeenCalled();
- expect(() => subject(mockStore, { errors: mockErrors }, {}, ...extraArgs)).toThrow();
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(errorMessage);
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/utils/design_management_utils_spec.js b/spec/frontend/design_management_legacy/utils/design_management_utils_spec.js
deleted file mode 100644
index 97e85a24a35..00000000000
--- a/spec/frontend/design_management_legacy/utils/design_management_utils_spec.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import {
- extractCurrentDiscussion,
- extractDiscussions,
- findVersionId,
- designUploadOptimisticResponse,
- updateImageDiffNoteOptimisticResponse,
- isValidDesignFile,
- extractDesign,
-} from '~/design_management_legacy/utils/design_management_utils';
-import mockResponseNoDesigns from '../mock_data/no_designs';
-import mockResponseWithDesigns from '../mock_data/designs';
-import mockDesign from '../mock_data/design';
-
-jest.mock('lodash/uniqueId', () => () => 1);
-
-describe('extractCurrentDiscussion', () => {
- let discussions;
-
- beforeEach(() => {
- discussions = {
- nodes: [
- { id: 101, payload: 'w' },
- { id: 102, payload: 'x' },
- { id: 103, payload: 'y' },
- { id: 104, payload: 'z' },
- ],
- };
- });
-
- it('finds the relevant discussion if it exists', () => {
- const id = 103;
- expect(extractCurrentDiscussion(discussions, id)).toEqual({ id, payload: 'y' });
- });
-
- it('returns null if the relevant discussion does not exist', () => {
- expect(extractCurrentDiscussion(discussions, 0)).not.toBeDefined();
- });
-});
-
-describe('extractDiscussions', () => {
- let discussions;
-
- beforeEach(() => {
- discussions = {
- nodes: [
- { id: 1, notes: { nodes: ['a'] } },
- { id: 2, notes: { nodes: ['b'] } },
- { id: 3, notes: { nodes: ['c'] } },
- { id: 4, notes: { nodes: ['d'] } },
- ],
- };
- });
-
- it('discards the edges.node artifacts of GraphQL', () => {
- expect(extractDiscussions(discussions)).toEqual([
- { id: 1, notes: ['a'], index: 1 },
- { id: 2, notes: ['b'], index: 2 },
- { id: 3, notes: ['c'], index: 3 },
- { id: 4, notes: ['d'], index: 4 },
- ]);
- });
-});
-
-describe('version parser', () => {
- it('correctly extracts version ID from a valid version string', () => {
- const testVersionId = '123';
- const testVersionString = `gid://gitlab/DesignManagement::Version/${testVersionId}`;
-
- expect(findVersionId(testVersionString)).toEqual(testVersionId);
- });
-
- it('fails to extract version ID from an invalid version string', () => {
- const testInvalidVersionString = `gid://gitlab/DesignManagement::Version`;
-
- expect(findVersionId(testInvalidVersionString)).toBeUndefined();
- });
-});
-
-describe('optimistic responses', () => {
- it('correctly generated for designManagementUpload', () => {
- const expectedResponse = {
- __typename: 'Mutation',
- designManagementUpload: {
- __typename: 'DesignManagementUploadPayload',
- designs: [
- {
- __typename: 'Design',
- id: -1,
- image: '',
- imageV432x230: '',
- filename: 'test',
- fullPath: '',
- notesCount: 0,
- event: 'NONE',
- diffRefs: { __typename: 'DiffRefs', baseSha: '', startSha: '', headSha: '' },
- discussions: { __typename: 'DesignDiscussion', nodes: [] },
- versions: {
- __typename: 'DesignVersionConnection',
- edges: {
- __typename: 'DesignVersionEdge',
- node: { __typename: 'DesignVersion', id: -1, sha: -1 },
- },
- },
- },
- ],
- errors: [],
- skippedDesigns: [],
- },
- };
- expect(designUploadOptimisticResponse([{ name: 'test' }])).toEqual(expectedResponse);
- });
-
- it('correctly generated for updateImageDiffNoteOptimisticResponse', () => {
- const mockNote = {
- id: 'test-note-id',
- };
-
- const mockPosition = {
- x: 10,
- y: 10,
- width: 10,
- height: 10,
- };
-
- const expectedResponse = {
- __typename: 'Mutation',
- updateImageDiffNote: {
- __typename: 'UpdateImageDiffNotePayload',
- note: {
- ...mockNote,
- position: mockPosition,
- },
- errors: [],
- },
- };
- expect(updateImageDiffNoteOptimisticResponse(mockNote, { position: mockPosition })).toEqual(
- expectedResponse,
- );
- });
-});
-
-describe('isValidDesignFile', () => {
- // test every filetype that Design Management supports
- // https://docs.gitlab.com/ee/user/project/issues/design_management.html#limitations
- it.each`
- mimetype | isValid
- ${'image/svg'} | ${true}
- ${'image/png'} | ${true}
- ${'image/jpg'} | ${true}
- ${'image/jpeg'} | ${true}
- ${'image/gif'} | ${true}
- ${'image/bmp'} | ${true}
- ${'image/tiff'} | ${true}
- ${'image/ico'} | ${true}
- ${'image/svg'} | ${true}
- ${'video/mpeg'} | ${false}
- ${'audio/midi'} | ${false}
- ${'application/octet-stream'} | ${false}
- `('returns $isValid for file type $mimetype', ({ mimetype, isValid }) => {
- expect(isValidDesignFile({ type: mimetype })).toBe(isValid);
- });
-});
-
-describe('extractDesign', () => {
- describe('with no designs', () => {
- it('returns undefined', () => {
- expect(extractDesign(mockResponseNoDesigns)).toBeUndefined();
- });
- });
-
- describe('with designs', () => {
- it('returns the first design available', () => {
- expect(extractDesign(mockResponseWithDesigns)).toEqual(mockDesign);
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/utils/error_messages_spec.js b/spec/frontend/design_management_legacy/utils/error_messages_spec.js
deleted file mode 100644
index 489ac23da4e..00000000000
--- a/spec/frontend/design_management_legacy/utils/error_messages_spec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- designDeletionError,
- designUploadSkippedWarning,
-} from '~/design_management_legacy/utils/error_messages';
-
-const mockFilenames = n =>
- Array(n)
- .fill(0)
- .map((_, i) => ({ filename: `${i + 1}.jpg` }));
-
-describe('Error message', () => {
- describe('designDeletionError', () => {
- const singularMsg = 'Could not delete a design. Please try again.';
- const pluralMsg = 'Could not delete designs. Please try again.';
-
- describe('when [singular=true]', () => {
- it.each([[undefined], [true]])('uses singular grammar', singularOption => {
- expect(designDeletionError({ singular: singularOption })).toEqual(singularMsg);
- });
- });
-
- describe('when [singular=false]', () => {
- it('uses plural grammar', () => {
- expect(designDeletionError({ singular: false })).toEqual(pluralMsg);
- });
- });
- });
-
- describe.each([
- [[], [], null],
- [mockFilenames(1), mockFilenames(1), 'Upload skipped. 1.jpg did not change.'],
- [
- mockFilenames(2),
- mockFilenames(2),
- 'Upload skipped. The designs you tried uploading did not change.',
- ],
- [
- mockFilenames(2),
- mockFilenames(1),
- 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg.',
- ],
- [
- mockFilenames(6),
- mockFilenames(5),
- 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg.',
- ],
- [
- mockFilenames(7),
- mockFilenames(6),
- 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, and 1 more.',
- ],
- [
- mockFilenames(8),
- mockFilenames(7),
- 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, and 2 more.',
- ],
- ])('designUploadSkippedWarning', (uploadedFiles, skippedFiles, expected) => {
- it('returns expected warning message', () => {
- expect(designUploadSkippedWarning(uploadedFiles, skippedFiles)).toBe(expected);
- });
- });
-});
diff --git a/spec/frontend/design_management_legacy/utils/tracking_spec.js b/spec/frontend/design_management_legacy/utils/tracking_spec.js
deleted file mode 100644
index a59cf80c906..00000000000
--- a/spec/frontend/design_management_legacy/utils/tracking_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { mockTracking } from 'helpers/tracking_helper';
-import { trackDesignDetailView } from '~/design_management_legacy/utils/tracking';
-
-function getTrackingSpy(key) {
- return mockTracking(key, undefined, jest.spyOn);
-}
-
-describe('Tracking Events', () => {
- describe('trackDesignDetailView', () => {
- const eventKey = 'projects:issues:design';
- const eventName = 'view_design';
-
- it('trackDesignDetailView fires a tracking event when called', () => {
- const trackingSpy = getTrackingSpy(eventKey);
-
- trackDesignDetailView();
-
- expect(trackingSpy).toHaveBeenCalledWith(
- eventKey,
- eventName,
- expect.objectContaining({
- label: eventName,
- context: {
- schema: expect.any(String),
- data: {
- 'design-version-number': 1,
- 'design-is-current-version': false,
- 'internal-object-referrer': '',
- 'design-collection-owner': '',
- },
- },
- }),
- );
- });
-
- it('trackDesignDetailView allows to customize the value payload', () => {
- const trackingSpy = getTrackingSpy(eventKey);
-
- trackDesignDetailView('from-a-test', 'test', 100, true);
-
- expect(trackingSpy).toHaveBeenCalledWith(
- eventKey,
- eventName,
- expect.objectContaining({
- label: eventName,
- context: {
- schema: expect.any(String),
- data: {
- 'design-version-number': 100,
- 'design-is-current-version': true,
- 'internal-object-referrer': 'from-a-test',
- 'design-collection-owner': 'test',
- },
- },
- }),
- );
- });
- });
-});
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index a80c9c25dbf..702937d61a7 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -78,6 +78,7 @@ describe('Incidents List', () => {
stubs: {
GlButton: true,
GlAvatar: true,
+ GlEmptyState: true,
},
});
}
@@ -96,12 +97,30 @@ describe('Incidents List', () => {
expect(findLoader().exists()).toBe(true);
});
- it('shows empty state', () => {
- mountComponent({
- data: { incidents: { list: [] }, incidentsCount: {} },
- loading: false,
- });
- expect(findEmptyState().exists()).toBe(true);
+ describe('empty state', () => {
+ const {
+ emptyState: { title, emptyClosedTabTitle, description },
+ } = I18N;
+
+ it.each`
+ statusFilter | all | closed | expectedTitle | expectedDescription
+ ${'all'} | ${2} | ${1} | ${title} | ${description}
+ ${'open'} | ${2} | ${0} | ${title} | ${description}
+ ${'closed'} | ${0} | ${0} | ${title} | ${description}
+ ${'closed'} | ${2} | ${0} | ${emptyClosedTabTitle} | ${undefined}
+ `(
+ `when active tab is $statusFilter and there are $all incidents in total and $closed closed incidents, the empty state
+ has title: $expectedTitle and description: $expectedDescription`,
+ ({ statusFilter, all, closed, expectedTitle, expectedDescription }) => {
+ mountComponent({
+ data: { incidents: { list: [] }, incidentsCount: { all, closed }, statusFilter },
+ loading: false,
+ });
+ expect(findEmptyState().exists()).toBe(true);
+ expect(findEmptyState().attributes('title')).toBe(expectedTitle);
+ expect(findEmptyState().attributes('description')).toBe(expectedDescription);
+ },
+ );
});
it('shows error state', () => {
@@ -188,6 +207,14 @@ describe('Incidents List', () => {
expect(findCreateIncidentBtn().attributes('loading')).toBe('true');
});
});
+
+ it("doesn't show the button when list is empty", () => {
+ mountComponent({
+ data: { incidents: { list: [] }, incidentsCount: {} },
+ loading: false,
+ });
+ expect(findCreateIncidentBtn().exists()).toBe(false);
+ });
});
describe('Pagination', () => {
@@ -313,7 +340,7 @@ describe('Incidents List', () => {
describe('Status Filter Tabs', () => {
beforeEach(() => {
mountComponent({
- data: { incidents: mockIncidents, incidentsCount },
+ data: { incidents: { list: mockIncidents }, incidentsCount },
loading: false,
stubs: {
GlTab: true,
@@ -345,7 +372,7 @@ describe('Incidents List', () => {
describe('sorting the incident list by column', () => {
beforeEach(() => {
mountComponent({
- data: { incidents: mockIncidents, incidentsCount },
+ data: { incidents: { list: mockIncidents }, incidentsCount },
loading: false,
});
});
diff --git a/spec/frontend/issuables_list/components/issuables_list_app_spec.js b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
index 9db03c49994..53a20ac69cb 100644
--- a/spec/frontend/issuables_list/components/issuables_list_app_spec.js
+++ b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
@@ -1,7 +1,11 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
-import { GlEmptyState, GlPagination, GlSkeletonLoading } from '@gitlab/ui';
+import {
+ GlEmptyState,
+ GlPagination,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
+} from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'helpers/test_constants';
import { deprecatedCreateFlash as flash } from '~/flash';
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index e757fe98661..502a1053663 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -1,6 +1,6 @@
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import ReleaseShowApp from '~/releases/components/app_show.vue';
import { release as originalRelease } from '../mock_data';
import ReleaseBlock from '~/releases/components/release_block.vue';
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 6f3e12a0789..1b8bbd5af6b 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlSkeletonLoading, GlButton } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
import Table from '~/repository/components/table/index.vue';
import TableRow from '~/repository/components/table/row.vue';
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
index 7dfb265c035..7fe6b44ecc7 100644
--- a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
+++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -1,4 +1,4 @@
-import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { invalidPlanWithName, plans, validPlanWithName } from './mock_data';
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index cd85a097460..b43bb6b10e0 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -1,4 +1,4 @@
-import { GlSkeletonLoading, GlSprintf, GlIcon } from '@gitlab/ui';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
deleted file mode 100644
index efa88d53f36..00000000000
--- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
- describe '#process' do
- subject { described_class.new }
-
- context 'when there is no GRPC exception' do
- let(:data) { { fingerprint: ['ArgumentError', 'Missing arguments'] } }
-
- it 'leaves data unchanged' do
- expect(subject.process(data)).to eq(data)
- end
- end
-
- context 'when there is a GPRC exception with a debug string' do
- let(:data) do
- {
- exception: {
- values: [
- {
- value: "GRPC::DeadlineExceeded: 4:DeadlineExceeded. debug_error_string:{\"hello\":1}"
- }
- ]
- },
- extra: {
- caller: 'test'
- },
- message: "GRPC::DeadlineExceeded: 4:DeadlineExceeded. debug_error_string:{\"hello\":1}",
- fingerprint: [
- "GRPC::DeadlineExceeded",
- "4:Deadline Exceeded. debug_error_string:{\"created\":\"@1598938192.005782000\",\"description\":\"Error received from peer unix:/home/git/gitalypraefect.socket\",\"file\":\"src/core/lib/surface/call.cc\",\"file_line\":1055,\"grpc_message\":\"Deadline Exceeded\",\"grpc_status\":4}"
- ]
- }
- end
-
- let(:expected) do
- {
- message: "GRPC::DeadlineExceeded: 4:DeadlineExceeded.",
- fingerprint: [
- "GRPC::DeadlineExceeded",
- "4:Deadline Exceeded."
- ],
- exception: {
- values: [
- {
- value: "GRPC::DeadlineExceeded: 4:DeadlineExceeded."
- }
- ]
- },
- extra: {
- caller: 'test',
- grpc_debug_error_string: "{\"hello\":1}"
- }
- }
- end
-
- it 'removes the debug error string and stores it as an extra field' do
- expect(subject.process(data)).to eq(expected)
- end
-
- context 'with no custom fingerprint' do
- before do
- data.delete(:fingerprint)
- expected.delete(:fingerprint)
- end
-
- it 'removes the debug error string and stores it as an extra field' do
- expect(subject.process(data)).to eq(expected)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/base_error_spec.rb b/spec/lib/gitlab/git/base_error_spec.rb
new file mode 100644
index 00000000000..851cfa16512
--- /dev/null
+++ b/spec/lib/gitlab/git/base_error_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+RSpec.describe Gitlab::Git::BaseError do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(message).to_s }
+
+ where(:message, :result) do
+ "GRPC::DeadlineExceeded: 4:DeadlineExceeded. debug_error_string:{\"hello\":1}" | "GRPC::DeadlineExceeded: 4:DeadlineExceeded."
+ "GRPC::DeadlineExceeded: 4:DeadlineExceeded." | "GRPC::DeadlineExceeded: 4:DeadlineExceeded."
+ "GRPC::DeadlineExceeded: 4:DeadlineExceeded. debug_error_string:{\"created\":\"@1598978902.544524530\",\"description\":\"Error received from peer ipv4: debug_error_string:test\"}" | "GRPC::DeadlineExceeded: 4:DeadlineExceeded."
+ "9:Multiple lines\nTest line. debug_error_string:{\"created\":\"@1599074877.106467000\"}" | "9:Multiple lines\nTest line."
+ "other message" | "other message"
+ nil | "Gitlab::Git::BaseError"
+ end
+
+ with_them do
+ it { is_expected.to eq(result) }
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb
new file mode 100644
index 00000000000..8f9a3e0cd9e
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::KubernetesAgentCounter do
+ it_behaves_like 'a redis usage counter', 'Kubernetes Agent', :gitops_sync
+
+ it_behaves_like 'a redis usage counter with totals', :kubernetes_agent, gitops_sync: 1
+
+ describe '.increment_gitops_sync' do
+ it 'increments the gtops_sync counter by the new increment amount' do
+ described_class.increment_gitops_sync(7)
+ described_class.increment_gitops_sync(2)
+ described_class.increment_gitops_sync(0)
+
+ expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 9)
+ end
+
+ it 'raises for negative numbers' do
+ expect { described_class.increment_gitops_sync(-1) }.to raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
index be528b081c5..d4f6110b3df 100644
--- a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -11,23 +11,47 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar
stub_application_setting(usage_ping_enabled: setting_value)
end
- context 'when usage_ping is disabled' do
- let(:setting_value) { false }
+ describe '.increment' do
+ context 'when usage_ping is disabled' do
+ let(:setting_value) { false }
+
+ it 'counter is not increased' do
+ expect do
+ subject.increment(redis_key)
+ end.not_to change { subject.total_count(redis_key) }
+ end
+ end
+
+ context 'when usage_ping is enabled' do
+ let(:setting_value) { true }
- it 'counter is not increased' do
- expect do
- subject.increment(redis_key)
- end.not_to change { subject.total_count(redis_key) }
+ it 'counter is increased' do
+ expect do
+ subject.increment(redis_key)
+ end.to change { subject.total_count(redis_key) }.by(1)
+ end
end
end
- context 'when usage_ping is enabled' do
- let(:setting_value) { true }
+ describe '.increment_by' do
+ context 'when usage_ping is disabled' do
+ let(:setting_value) { false }
+
+ it 'counter is not increased' do
+ expect do
+ subject.increment_by(redis_key, 3)
+ end.not_to change { subject.total_count(redis_key) }
+ end
+ end
+
+ context 'when usage_ping is enabled' do
+ let(:setting_value) { true }
- it 'counter is increased' do
- expect do
- subject.increment(redis_key)
- end.to change { subject.total_count(redis_key) }.by(1)
+ it 'counter is increased' do
+ expect do
+ subject.increment_by(redis_key, 3)
+ end.to change { subject.total_count(redis_key) }.by(3)
+ end
end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index dc7bfc6e870..c80e15055c6 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -575,6 +575,12 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
+ describe '.usage_counters' do
+ subject { described_class.usage_counters }
+
+ it { is_expected.to include(:kubernetes_agent_gitops_sync) }
+ end
+
describe '.usage_data_counters' do
subject { described_class.usage_data_counters }
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index a802e3a858a..ae5b6a9c4c6 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -15,11 +15,7 @@ RSpec.describe API::Internal::Kubernetes do
allow(Gitlab::Kas).to receive(:secret).and_return(jwt_secret)
end
- describe "GET /internal/kubernetes/agent_info" do
- def send_request(headers: {}, params: {})
- get api('/internal/kubernetes/agent_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
- end
-
+ shared_examples 'authorization' do
context 'not authenticated' do
it 'returns 401' do
send_request(headers: { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => '' })
@@ -28,6 +24,20 @@ RSpec.describe API::Internal::Kubernetes do
end
end
+ context 'authenticated' do
+ it 'returns 403 if Authorization header not sent' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 404 if Authorization is for non-existent agent' do
+ send_request(headers: { 'Authorization' => 'Bearer NONEXISTENT' })
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'kubernetes_agent_internal_api feature flag disabled' do
before do
stub_feature_flags(kubernetes_agent_internal_api: false)
@@ -39,12 +49,50 @@ RSpec.describe API::Internal::Kubernetes do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+ end
+
+ describe 'POST /internal/kubernetes/usage_metrics' do
+ def send_request(headers: {}, params: {})
+ post api('/internal/kubernetes/usage_metrics'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+ end
- it 'returns 403 if Authorization header not sent' do
- send_request
+ include_examples 'authorization'
- expect(response).to have_gitlab_http_status(:forbidden)
+ context 'is authenticated for an agent' do
+ let!(:agent_token) { create(:cluster_agent_token) }
+
+ it 'returns no_content for valid gitops_sync_count' do
+ send_request(params: { gitops_sync_count: 10 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'returns no_content 0 gitops_sync_count' do
+ send_request(params: { gitops_sync_count: 0 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'returns 400 for non number' do
+ send_request(params: { gitops_sync_count: 'string' }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns 400 for negative number' do
+ send_request(params: { gitops_sync_count: '-1' }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
end
+ end
+
+ describe "GET /internal/kubernetes/agent_info" do
+ def send_request(headers: {}, params: {})
+ get api('/internal/kubernetes/agent_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+ end
+
+ include_examples 'authorization'
context 'an agent is found' do
let!(:agent_token) { create(:cluster_agent_token) }
@@ -77,14 +125,6 @@ RSpec.describe API::Internal::Kubernetes do
)
end
end
-
- context 'no such agent exists' do
- it 'returns 404' do
- send_request(headers: { 'Authorization' => 'Bearer ABCD' })
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
end
describe 'GET /internal/kubernetes/project_info' do
@@ -92,39 +132,7 @@ RSpec.describe API::Internal::Kubernetes do
get api('/internal/kubernetes/project_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
- context 'not authenticated' do
- it 'returns 401' do
- send_request(headers: { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => '' })
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
- context 'kubernetes_agent_internal_api feature flag disabled' do
- before do
- stub_feature_flags(kubernetes_agent_internal_api: false)
- end
-
- it 'returns 404' do
- send_request
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- it 'returns 403 if Authorization header not sent' do
- send_request
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- context 'no such agent exists' do
- it 'returns 404' do
- send_request(headers: { 'Authorization' => 'Bearer ABCD' })
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
+ include_examples 'authorization'
context 'an agent is found' do
let!(:agent_token) { create(:cluster_agent_token) }