Commit 3896d1c3 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '12928-design-management-displays-broken-image-while-uploading' into 'master'

Show loading spinner in design card while design is uploading

See merge request gitlab-org/gitlab!20814
parents d7b8bd27 59328941
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import { n__, __ } from '~/locale'; import { n__, __ } from '~/locale';
export default { export default {
components: { components: {
GlLoadingIcon,
Icon, Icon,
Timeago, Timeago,
}, },
...@@ -34,6 +36,11 @@ export default { ...@@ -34,6 +36,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
isLoading: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
icon() { icon() {
...@@ -74,13 +81,15 @@ export default { ...@@ -74,13 +81,15 @@ export default {
}" }"
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
> >
<div class="card-body p-0 d-flex align-items-center overflow-hidden position-relative"> <div class="card-body p-0 d-flex-center overflow-hidden position-relative">
<div v-if="icon.name" class="design-event position-absolute"> <div v-if="icon.name" class="design-event position-absolute">
<span :title="icon.tooltip" :aria-label="icon.tooltip"> <span :title="icon.tooltip" :aria-label="icon.tooltip">
<icon :name="icon.name" :size="18" :class="icon.classes" /> <icon :name="icon.name" :size="18" :class="icon.classes" />
</span> </span>
</div> </div>
<gl-loading-icon v-if="isLoading" size="md" />
<img <img
v-else
:src="image" :src="image"
:alt="filename" :alt="filename"
class="block ml-auto mr-auto mw-100 mh-100 design-img" class="block ml-auto mr-auto mw-100 mh-100 design-img"
......
...@@ -46,7 +46,7 @@ export default { ...@@ -46,7 +46,7 @@ export default {
permissions: { permissions: {
createDesign: false, createDesign: false,
}, },
isSaving: false, filesToBeSaved: [],
selectedDesigns: [], selectedDesigns: [],
}; };
}, },
...@@ -54,6 +54,9 @@ export default { ...@@ -54,6 +54,9 @@ export default {
isLoading() { isLoading() {
return this.$apollo.queries.designs.loading || this.$apollo.queries.permissions.loading; return this.$apollo.queries.designs.loading || this.$apollo.queries.permissions.loading;
}, },
isSaving() {
return this.filesToBeSaved.length > 0;
},
canCreateDesign() { canCreateDesign() {
return this.permissions.createDesign; return this.permissions.createDesign;
}, },
...@@ -100,7 +103,8 @@ export default { ...@@ -100,7 +103,8 @@ export default {
return null; return null;
} }
const optimisticResponse = Array.from(files).map(file => ({ this.filesToBeSaved = Array.from(files);
const optimisticResponse = this.filesToBeSaved.map(file => ({
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
__typename: 'Design', __typename: 'Design',
...@@ -133,8 +137,6 @@ export default { ...@@ -133,8 +137,6 @@ export default {
}, },
})); }));
this.isSaving = true;
return this.$apollo return this.$apollo
.mutate({ .mutate({
mutation: uploadDesignMutation, mutation: uploadDesignMutation,
...@@ -167,7 +169,7 @@ export default { ...@@ -167,7 +169,7 @@ export default {
throw e; throw e;
}) })
.finally(() => { .finally(() => {
this.isSaving = false; this.filesToBeSaved = [];
}); });
}, },
changeSelectedDesigns(filename) { changeSelectedDesigns(filename) {
...@@ -187,6 +189,12 @@ export default { ...@@ -187,6 +189,12 @@ export default {
isDesignSelected(filename) { isDesignSelected(filename) {
return this.selectedDesigns.includes(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() { onDesignDelete() {
this.selectedDesigns = []; this.selectedDesigns = [];
if (this.$route.query.version) this.$router.push({ name: 'designs' }); if (this.$route.query.version) this.$router.push({ name: 'designs' });
...@@ -241,9 +249,9 @@ export default { ...@@ -241,9 +249,9 @@ export default {
</div> </div>
<ol v-else-if="hasDesigns" class="list-unstyled row"> <ol v-else-if="hasDesigns" class="list-unstyled row">
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-4 mb-3"> <li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-4 mb-3">
<design v-bind="design" /> <design v-bind="design" :is-loading="isDesignToBeSaved(design.filename)" />
<input <input
v-if="isLatestVersion && canCreateDesign" v-if="canSelectDesign(design.filename)"
:checked="isDesignSelected(design.filename)" :checked="isDesignSelected(design.filename)"
type="checkbox" type="checkbox"
class="design-checkbox" class="design-checkbox"
......
---
title: Show loading spinner in design card while design is uploading
merge_request: 20814
author:
type: added
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management list item component hides comment count 1`] = ` exports[`Design management list item component with no notes renders item with correct status icon for creation event 1`] = `
<router-link-stub <router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]" to="[object Object]"
> >
<div <div
class="card-body p-0 d-flex align-items-center overflow-hidden position-relative" class="card-body p-0 d-flex-center overflow-hidden position-relative"
>
<!---->
<img
alt="test"
class="block ml-auto mr-auto mw-100 mh-100 design-img"
data-qa-selector="design_image"
src="http://via.placeholder.com/300"
/>
</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>
</router-link-stub>
`;
exports[`Design management list item component renders item with correct status icon for creation event 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 align-items-center overflow-hidden position-relative"
> >
<div <div
class="design-event position-absolute" class="design-event position-absolute"
...@@ -111,13 +62,13 @@ exports[`Design management list item component renders item with correct status ...@@ -111,13 +62,13 @@ exports[`Design management list item component renders item with correct status
</router-link-stub> </router-link-stub>
`; `;
exports[`Design management list item component renders item with correct status icon for deletion event 1`] = ` exports[`Design management list item component with no notes renders item with correct status icon for deletion event 1`] = `
<router-link-stub <router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]" to="[object Object]"
> >
<div <div
class="card-body p-0 d-flex align-items-center overflow-hidden position-relative" class="card-body p-0 d-flex-center overflow-hidden position-relative"
> >
<div <div
class="design-event position-absolute" class="design-event position-absolute"
...@@ -173,13 +124,13 @@ exports[`Design management list item component renders item with correct status ...@@ -173,13 +124,13 @@ exports[`Design management list item component renders item with correct status
</router-link-stub> </router-link-stub>
`; `;
exports[`Design management list item component renders item with correct status icon for modification event 1`] = ` exports[`Design management list item component with no notes renders item with correct status icon for modification event 1`] = `
<router-link-stub <router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]" to="[object Object]"
> >
<div <div
class="card-body p-0 d-flex align-items-center overflow-hidden position-relative" class="card-body p-0 d-flex-center overflow-hidden position-relative"
> >
<div <div
class="design-event position-absolute" class="design-event position-absolute"
...@@ -235,13 +186,13 @@ exports[`Design management list item component renders item with correct status ...@@ -235,13 +186,13 @@ exports[`Design management list item component renders item with correct status
</router-link-stub> </router-link-stub>
`; `;
exports[`Design management list item component renders item with multiple comments 1`] = ` exports[`Design management list item component with notes renders item with multiple comments 1`] = `
<router-link-stub <router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]" to="[object Object]"
> >
<div <div
class="card-body p-0 d-flex align-items-center overflow-hidden position-relative" class="card-body p-0 d-flex-center overflow-hidden position-relative"
> >
<!----> <!---->
...@@ -301,13 +252,13 @@ exports[`Design management list item component renders item with multiple commen ...@@ -301,13 +252,13 @@ exports[`Design management list item component renders item with multiple commen
</router-link-stub> </router-link-stub>
`; `;
exports[`Design management list item component renders item with no status icon for none event 1`] = ` exports[`Design management list item component with no notes renders item with no status icon for none event 1`] = `
<router-link-stub <router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]" to="[object Object]"
> >
<div <div
class="card-body p-0 d-flex align-items-center overflow-hidden position-relative" class="card-body p-0 d-flex-center overflow-hidden position-relative"
> >
<!----> <!---->
...@@ -350,13 +301,13 @@ exports[`Design management list item component renders item with no status icon ...@@ -350,13 +301,13 @@ exports[`Design management list item component renders item with no status icon
</router-link-stub> </router-link-stub>
`; `;
exports[`Design management list item component renders item with single comment 1`] = ` exports[`Design management list item component with notes renders item with single comment 1`] = `
<router-link-stub <router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item" class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]" to="[object Object]"
> >
<div <div
class="card-body p-0 d-flex align-items-center overflow-hidden position-relative" class="card-body p-0 d-flex-center overflow-hidden position-relative"
> >
<!----> <!---->
...@@ -415,3 +366,51 @@ exports[`Design management list item component renders item with single comment ...@@ -415,3 +366,51 @@ exports[`Design management list item component renders item with single comment
</div> </div>
</router-link-stub> </router-link-stub>
`; `;
exports[`Design management list item component with no notes renders loading spinner when isLoading is true 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"
>
<!---->
<glloadingicon-stub
color="orange"
label="Loading"
size="md"
/>
</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>
</router-link-stub>
`;
...@@ -6,10 +6,21 @@ const localVue = createLocalVue(); ...@@ -6,10 +6,21 @@ const localVue = createLocalVue();
localVue.use(VueRouter); localVue.use(VueRouter);
const router = new 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', () => { describe('Design management list item component', () => {
let wrapper; let wrapper;
function createComponent({
function createComponent(notesCount = 1, event = 'NONE') { notesCount = 0,
event = DESIGN_VERSION_EVENT.NO_CHANGE,
isLoading = false,
} = {}) {
wrapper = shallowMount(Item, { wrapper = shallowMount(Item, {
sync: false, sync: false,
localVue, localVue,
...@@ -18,6 +29,7 @@ describe('Design management list item component', () => { ...@@ -18,6 +29,7 @@ describe('Design management list item component', () => {
id: 1, id: 1,
filename: 'test', filename: 'test',
image: 'http://via.placeholder.com/300', image: 'http://via.placeholder.com/300',
isLoading,
event, event,
notesCount, notesCount,
updatedAt: '01-01-2019', updatedAt: '01-01-2019',
...@@ -30,45 +42,49 @@ describe('Design management list item component', () => { ...@@ -30,45 +42,49 @@ describe('Design management list item component', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders item with single comment', () => { describe('with notes', () => {
createComponent(); it('renders item with single comment', () => {
createComponent({ notesCount: 1 });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('renders item with multiple comments', () => { it('renders item with multiple comments', () => {
createComponent(2); createComponent({ notesCount: 2 });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
});
}); });
it('hides comment count', () => { describe('with no notes', () => {
createComponent(0); it('renders item with no status icon for none event', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('renders item with correct status icon for modification event', () => { it('renders item with correct status icon for modification event', () => {
createComponent(0, 'MODIFICATION'); createComponent({ event: DESIGN_VERSION_EVENT.MODIFICATION });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('renders item with correct status icon for deletion event', () => { it('renders item with correct status icon for deletion event', () => {
createComponent(0, 'DELETION'); createComponent({ event: DESIGN_VERSION_EVENT.DELETION });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('renders item with correct status icon for creation event', () => { it('renders item with correct status icon for creation event', () => {
createComponent(0, 'CREATION'); createComponent({ event: DESIGN_VERSION_EVENT.CREATION });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('renders item with no status icon for none event', () => { it('renders loading spinner when isLoading is true', () => {
createComponent(0, 'NONE'); createComponent({ isLoading: true });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
});
}); });
}); });
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