Commit dd4092bb authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'design-dropzone-design-cards' into 'master'

Make design cards dropzones

See merge request gitlab-org/gitlab!26446
parents c4409050 67d2ff37
......@@ -77,17 +77,18 @@ Navigate to the **Design Management** page from any issue by clicking the **Desi
To upload design images, click the **Upload Designs** button and select images to upload.
Designs with the same filename as an existing uploaded design will create a new version
of the design, and will replace the previous version.
Designs cannot be added if the issue has been moved, or its
[discussion is locked](../../discussions/#lock-discussions).
[Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34353) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.9,
you can drag and drop designs onto the dedicated dropzone to upload them.
![Drag and drop design uploads](img/design_drag_and_drop_uploads_v12_9.png)
Designs with the same filename as an existing uploaded design will create a new version
of the design, and will replace the previous version. [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34353) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.9, dropping a design on an existing uploaded design will also create a new version,
provided the filenames are the same.
Designs cannot be added if the issue has been moved, or its
[discussion is locked](../../discussions/#lock-discussions).
### Skipped designs
Designs with the same filename as an existing uploaded design _and_ whose content has not changed will be skipped.
......
......@@ -32,7 +32,7 @@ export default {
isValidDragDataType({ dataTransfer }) {
return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE));
},
ondrop({ dataTransfer }) {
ondrop({ dataTransfer = {} }) {
this.dragCounter = 0;
// User already had feedback when dropzone was active, so bail here
if (!this.isDragDataValid) {
......@@ -45,7 +45,7 @@ export default {
return;
}
this.$emit('upload', files);
this.$emit('change', files);
},
ondragenter(e) {
this.dragCounter += 1;
......@@ -58,7 +58,7 @@ export default {
this.$refs.fileUpload.$el.click();
},
onDesignInputChange(e) {
this.$emit('upload', e.target.files);
this.$emit('change', e.target.files);
},
},
uploadDesignMutation,
......
......@@ -14,6 +14,8 @@ import projectQuery from '../graphql/queries/project.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';
......@@ -197,6 +199,21 @@ export default {
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);
},
},
beforeRouteUpdate(to, from, next) {
this.selectedDesigns = [];
......@@ -248,10 +265,12 @@ export default {
</div>
<ol v-else class="list-unstyled row">
<li class="col-md-6 col-lg-4 mb-3">
<design-dropzone class="design-list-item" @upload="onUploadDesign" />
<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 v-bind="design" :is-loading="isDesignToBeSaved(design.filename)" />
<design-dropzone @change="onExistingDesignDropzoneChange($event, design.filename)"
><design v-bind="design" :is-loading="isDesignToBeSaved(design.filename)"
/></design-dropzone>
<input
v-if="canSelectDesign(design.filename)"
......
......@@ -30,6 +30,14 @@ 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 =>
......
---
title: Support drag-and-drop on existing designs in Design Management
merge_request: 26446
author:
type: added
......@@ -101,7 +101,7 @@ describe('Design management dropzone component', () => {
return wrapper.vm.$nextTick();
})
.then(() => {
expect(wrapper.emitted().upload[0]).toEqual([[mockFile]]);
expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
});
});
});
......@@ -117,7 +117,7 @@ describe('Design management dropzone component', () => {
const mockEvent = mockDragEvent({ files: [mockFile] });
wrapper.vm.ondrop(mockEvent);
expect(wrapper.emitted().upload[0]).toEqual([[mockFile]]);
expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
});
it('calls createFlash when files are invalid', () => {
......
......@@ -21,6 +21,7 @@ exports[`Design management index page designs does not render toolbar when there
<li
class="col-md-6 col-lg-4 mb-3"
>
<design-dropzone-stub>
<design-stub
event="NONE"
filename="design-1-name"
......@@ -28,12 +29,14 @@ exports[`Design management index page designs does not render toolbar when there
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"
......@@ -41,12 +44,14 @@ exports[`Design management index page designs does not render toolbar when there
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"
......@@ -54,6 +59,7 @@ exports[`Design management index page designs does not render toolbar when there
image="design-3-image"
notescount="0"
/>
</design-dropzone-stub>
<!---->
</li>
......@@ -121,6 +127,7 @@ exports[`Design management index page designs renders designs list and header wi
<li
class="col-md-6 col-lg-4 mb-3"
>
<design-dropzone-stub>
<design-stub
event="NONE"
filename="design-1-name"
......@@ -128,6 +135,7 @@ exports[`Design management index page designs renders designs list and header wi
image="design-1-image"
notescount="0"
/>
</design-dropzone-stub>
<input
class="design-checkbox"
......@@ -137,6 +145,7 @@ exports[`Design management index page designs renders designs list and header wi
<li
class="col-md-6 col-lg-4 mb-3"
>
<design-dropzone-stub>
<design-stub
event="NONE"
filename="design-2-name"
......@@ -144,6 +153,7 @@ exports[`Design management index page designs renders designs list and header wi
image="design-2-image"
notescount="1"
/>
</design-dropzone-stub>
<input
class="design-checkbox"
......@@ -153,6 +163,7 @@ exports[`Design management index page designs renders designs list and header wi
<li
class="col-md-6 col-lg-4 mb-3"
>
<design-dropzone-stub>
<design-stub
event="NONE"
filename="design-3-name"
......@@ -160,6 +171,7 @@ exports[`Design management index page designs renders designs list and header wi
image="design-3-image"
notescount="0"
/>
</design-dropzone-stub>
<input
class="design-checkbox"
......
......@@ -9,6 +9,10 @@ import DesignDestroyer from 'ee/design_management/components/design_destroyer.vu
import DesignDropzone from 'ee/design_management/components/upload/design_dropzone.vue';
import DeleteButton from 'ee/design_management/components/delete_button.vue';
import { DESIGNS_ROUTE_NAME } from 'ee/design_management/router/constants';
import {
EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
} from 'ee/design_management/utils/error_messages';
import createFlash from '~/flash';
const localVue = createLocalVue();
......@@ -63,7 +67,8 @@ describe('Design management index page', () => {
const findSelectAllButton = () => wrapper.find('.js-select-all');
const findToolbar = () => wrapper.find('.qa-selector-toolbar');
const findDeleteButton = () => wrapper.find(DeleteButton);
const findDropzone = () => wrapper.find(DesignDropzone);
const findDropzone = () => wrapper.findAll(DesignDropzone).at(0);
const findFirstDropzoneWithDesign = () => wrapper.findAll(DesignDropzone).at(1);
function createComponent({
loading = false,
......@@ -225,7 +230,7 @@ describe('Design management index page', () => {
};
return wrapper.vm.$nextTick().then(() => {
findDropzone().vm.$emit('upload', [{ name: 'test' }]);
findDropzone().vm.$emit('change', [{ name: 'test' }]);
expect(mutate).toHaveBeenCalledWith(mutationVariables);
expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
expect(wrapper.vm.isSaving).toBeTruthy();
......@@ -328,6 +333,42 @@ describe('Design management index page', () => {
);
});
});
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', () => {
......
......@@ -22791,6 +22791,9 @@ msgstr ""
msgid "You can only transfer the project to namespaces you manage."
msgstr ""
msgid "You can only upload one design when dropping onto an existing design."
msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
......@@ -22944,6 +22947,9 @@ msgstr ""
msgid "You must set up incoming email before it becomes active."
msgstr ""
msgid "You must upload a file with the same file name when dropping onto an existing design."
msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment