Commit 74f4c11f authored by Samantha Ming's avatar Samantha Ming Committed by Phil Hughes

Implement view previous designs versions

- Has version selection dropdown
- Repopulate designs when a version is selected
parent 092b0055
......@@ -43,7 +43,7 @@ export default {
<template>
<router-link
:to="{ name: 'design', params: { id: name } }"
class="card cursor-pointer text-plain js-design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
>
<div class="card-body p-0">
<img :src="image" :alt="name" class="block ml-auto mr-auto mw-100 design-img" height="230" />
......@@ -64,9 +64,3 @@ export default {
</div>
</router-link>
</template>
<style scoped>
.card:hover {
text-decoration: none;
}
</style>
......@@ -11,11 +11,6 @@ export default {
type: Boolean,
required: true,
},
isInverted: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
openFileUpload() {
......@@ -30,15 +25,8 @@ export default {
<template>
<div>
<gl-button
:disabled="isSaving"
:class="{
'btn-inverted': isInverted,
}"
variant="primary"
@click="openFileUpload"
>
{{ s__('DesignManagement|Upload designs') }}
<gl-button :disabled="isSaving" variant="primary" @click="openFileUpload">
{{ s__('DesignManagement|Add designs') }}
<gl-loading-icon v-if="isSaving" inline class="ml-1" />
</gl-button>
<input
......
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import allVersionsMixin from '../../mixins/all_versions';
export default {
components: {
GlDropdown,
GlDropdownItem,
},
mixins: [allVersionsMixin],
computed: {
dropdownText() {
if (
!this.$route.query.version ||
Number(this.$route.query.version) === this.allVersions.length
) {
return __('Showing Latest Version');
}
const versionNumber = this.getCurrentVersionNumber();
return sprintf(__('Showing Version #%{versionNumber}'), {
versionNumber,
});
},
currentVersion() {
return this.$route.query.version || this.getLatestVersionId();
},
},
methods: {
getVersionId(versionId) {
return versionId.match('::Version\/(.+$)')[1]; // eslint-disable-line no-useless-escape
},
getLatestVersionId() {
return this.getVersionId(this.allVersions[0].node.id);
},
getCurrentVersionNumber() {
const versionIndex = this.allVersions.findIndex(
version => this.getVersionId(version.node.id) === this.$route.query.version,
);
return this.allVersions.length - versionIndex;
},
},
};
</script>
<template>
<gl-dropdown :text="dropdownText" variant="link" class="design-version-dropdown">
<gl-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: getVersionId(version.node.id) } }"
>
<div class="flex-grow-1 ml-2">
<div>
<strong
>{{ __('Version') }} {{ allVersions.length - index }}
<span v-if="getVersionId(version.node.id) === getLatestVersionId()"
>({{ __('latest') }})</span
>
</strong>
</div>
</div>
<i
v-if="getVersionId(version.node.id) === currentVersion"
class="fa fa-check pull-right"
></i>
</router-link>
</gl-dropdown-item>
</gl-dropdown>
</template>
<script>
import UploadButton from './button.vue';
import allVersionsMixin from '../../mixins/all_versions';
import DesignVersionDropdown from './design_version_dropdown.vue';
export default {
components: {
UploadButton,
DesignVersionDropdown,
},
mixins: [allVersionsMixin],
props: {
isSaving: {
type: Boolean,
......@@ -25,13 +29,9 @@ export default {
<template>
<header class="row-content-block border-top-0 p-2 d-flex">
<div>
<upload-button
v-if="canUploadDesign"
:is-saving="isSaving"
:is-inverted="true"
@upload="onFileUploadChange"
/>
<div class="d-flex justify-content-between align-items-center w-100">
<design-version-dropdown :all-versions="allVersions" />
<upload-button v-if="canUploadDesign" :is-saving="isSaving" @upload="onFileUploadChange" />
</div>
</header>
</template>
import appDataQuery from '../queries/appData.graphql';
import allDesignsQuery from '../queries/allDesigns.graphql';
import getVersionDesignsQuery from '../queries/getVersionDesigns.query.graphql';
import projectQuery from '../queries/project.query.graphql';
export default {
apollo: {
......@@ -12,7 +13,7 @@ export default {
},
},
designs: {
query: allDesignsQuery,
query: projectQuery,
variables() {
return {
fullPath: this.projectPath,
......@@ -24,6 +25,21 @@ export default {
this.error = true;
},
},
versionDesigns: {
query: getVersionDesignsQuery,
fetchPolicy: 'no-cache',
variables() {
return {
fullPath: this.projectPath,
iid: this.issueIid,
atVersion: `gid://gitlab/DesignManagement::Version/${this.$route.query.version}`,
};
},
skip() {
this.$apollo.queries.versionDesigns.skip = !this.hasValidVersion();
},
update: data => data.project.issue.designs.designs.edges.map(({ node }) => node),
},
},
data() {
return {
......@@ -31,6 +47,15 @@ export default {
error: false,
projectPath: '',
issueIid: null,
versionDesigns: [],
};
},
methods: {
hasValidVersion() {
if (Object.keys(this.$route.query).length === 0) {
return false;
}
return this.allVersions.some(version => version.node.id.endsWith(this.$route.query.version));
},
},
};
export default {
props: {
allVersions: {
type: Array,
required: true,
},
},
};
......@@ -6,10 +6,10 @@ import { s__, sprintf } from '~/locale';
import DesignList from '../components/list/index.vue';
import UploadForm from '../components/upload/form.vue';
import EmptyState from '../components/empty_state.vue';
import allDesignsQuery from '../queries/allDesigns.graphql';
import uploadDesignMutation from '../queries/uploadDesign.graphql';
import permissionsQuery from '../queries/permissions.graphql';
import allDesignsMixin from '../mixins/all_designs';
import projectQuery from '../queries/project.query.graphql';
const MAXIMUM_FILE_UPLOAD_LIMIT = 10;
......@@ -32,6 +32,16 @@ export default {
},
update: data => data.project.issue.userPermissions,
},
allVersions: {
query: projectQuery,
variables() {
return {
fullPath: this.projectPath,
iid: this.issueIid,
};
},
update: data => data.project.issue.designs.versions.edges,
},
},
data() {
return {
......@@ -39,6 +49,7 @@ export default {
createDesign: false,
},
isSaving: false,
allVersions: [],
};
},
computed: {
......@@ -54,6 +65,9 @@ export default {
hasDesigns() {
return this.designs.length > 0;
},
hasVersion() {
return this.hasValidVersion();
},
},
methods: {
onUploadDesign(files) {
......@@ -79,6 +93,17 @@ export default {
id: -_.uniqueId(),
image: '',
filename: file.name,
versions: {
__typename: 'DesignVersionConnection',
edges: {
__typename: 'DesignVersionEdge',
node: {
__typename: 'DesignVersion',
id: -_.uniqueId(),
sha: -_.uniqueId(),
},
},
},
}));
this.isSaving = true;
......@@ -96,9 +121,10 @@ export default {
},
update: (store, { data: { designManagementUpload } }) => {
const data = store.readQuery({
query: allDesignsQuery,
query: projectQuery,
variables: { fullPath: this.projectPath, iid: this.issueIid },
});
const newDesigns = data.project.issue.designs.designs.edges.reduce((acc, design) => {
if (!acc.find(d => d.filename === design.node.filename)) {
acc.push(design.node);
......@@ -106,9 +132,27 @@ export default {
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.designs.versions.edges,
];
const newQueryData = {
project: {
__typename: 'Project',
id: '',
issue: {
__typename: 'Issue',
designs: {
......@@ -120,13 +164,17 @@ export default {
node: design,
})),
},
versions: {
__typename: 'DesignVersionConnection',
edges: newVersions,
},
},
},
},
};
store.writeQuery({
query: allDesignsQuery,
query: projectQuery,
variables: { fullPath: this.projectPath, iid: this.issueIid },
data: newQueryData,
});
......@@ -141,6 +189,7 @@ export default {
})
.then(() => {
this.isSaving = false;
this.$router.push('/designs');
})
.catch(e => {
this.isSaving = false;
......@@ -160,6 +209,7 @@ export default {
v-if="showUploadForm"
:can-upload-design="canCreateDesign"
:is-saving="isSaving"
:all-versions="allVersions"
@upload="onUploadDesign"
/>
<div class="mt-4">
......@@ -167,6 +217,7 @@ export default {
<div v-else-if="error" class="alert alert-danger">
{{ __('An error occurred while loading designs. Please try again.') }}
</div>
<design-list v-else-if="hasVersion" :designs="versionDesigns" />
<design-list v-else-if="hasDesigns" :designs="designs" />
<empty-state
v-else
......
#import "./designListFragment.graphql"
query getVersionDesigns($fullPath: ID!, $iid: String!, $atVersion: ID!) {
project(fullPath: $fullPath) {
id
issue(iid: $iid) {
iid
designs {
designs(atVersion: $atVersion) {
edges {
node {
...DesignListItem
}
}
}
}
}
}
}
query permissions($fullPath: ID!, $iid: String!) {
project(fullPath: $fullPath) {
id
issue(iid: $iid) {
userPermissions {
createDesign
......
#import "./designListFragment.graphql"
query project($fullPath: ID!, $iid: String!) {
project(fullPath: $fullPath) {
id
issue(iid: $iid) {
designs {
designs {
edges {
node {
...DesignListItem
}
}
}
versions {
edges {
node {
id
sha
}
}
}
}
}
}
}
......@@ -4,6 +4,14 @@ mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) {
designs {
...DesignListItem
versions {
edges {
node {
id
sha
}
}
}
}
}
}
.design-list-item {
text-decoration: none;
.icon-version-status {
position: absolute;
right: 10px;
top: 10px;
}
}
.design-version-dropdown > button {
background: inherit;
}
---
title: Add ability to view different design versions
merge_request: 14601
author:
type: added
......@@ -2,7 +2,7 @@
exports[`Design management list item component hides comment count 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]"
>
<div
......@@ -48,7 +48,7 @@ exports[`Design management list item component hides comment count 1`] = `
exports[`Design management list item component renders item with multiple comments 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]"
>
<div
......@@ -112,7 +112,7 @@ exports[`Design management list item component renders item with multiple commen
exports[`Design management list item component renders item with single comment 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
to="[object Object]"
>
<div
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management upload button component renders inverted upload design button 1`] = `
<div>
<div
isinverted="true"
>
<glbutton-stub
class="btn-inverted"
variant="primary"
>
Upload designs
Add designs
<!---->
</glbutton-stub>
......@@ -25,12 +26,11 @@ exports[`Design management upload button component renders inverted upload desig
exports[`Design management upload button component renders loading icon 1`] = `
<div>
<glbutton-stub
class=""
disabled="true"
variant="primary"
>
Upload designs
Add designs
<glloadingicon-stub
class="ml-1"
......@@ -54,11 +54,10 @@ exports[`Design management upload button component renders loading icon 1`] = `
exports[`Design management upload button component renders upload design button 1`] = `
<div>
<glbutton-stub
class=""
variant="primary"
>
Upload designs
Add designs
<!---->
</glbutton-stub>
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
<gldropdown-stub
class="design-version-dropdown"
issueiid=""
projectpath=""
text="Showing Latest Version"
variant="link"
>
<gldropdownitem-stub>
<routerlink-stub
class="d-flex js-version-link"
event="click"
tag="a"
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 pull-right"
/>
</routerlink-stub>
</gldropdownitem-stub>
<gldropdownitem-stub>
<routerlink-stub
class="d-flex js-version-link"
event="click"
tag="a"
to="[object Object]"
>
<div
class="flex-grow-1 ml-2"
>
<div>
<strong>
Version 1
<!---->
</strong>
</div>
</div>
<!---->
</routerlink-stub>
</gldropdownitem-stub>
</gldropdown-stub>
`;
exports[`Design management design version dropdown component renders design version list 1`] = `
<gldropdown-stub
class="design-version-dropdown"
issueiid=""
projectpath=""
text="Showing Latest Version"
variant="link"
>
<gldropdownitem-stub>
<routerlink-stub
class="d-flex js-version-link"
event="click"
tag="a"
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 pull-right"
/>
</routerlink-stub>
</gldropdownitem-stub>
<gldropdownitem-stub>
<routerlink-stub
class="d-flex js-version-link"
event="click"
tag="a"
to="[object Object]"
>
<div
class="flex-grow-1 ml-2"
>
<div>
<strong>
Version 1
<!---->
</strong>
</div>
</div>
<!---->
</routerlink-stub>
</gldropdownitem-stub>
</gldropdown-stub>
`;
......@@ -3,8 +3,16 @@
exports[`Design management upload form component hides button if cant upload 1`] = `
<header
class="row-content-block border-top-0 p-2 d-flex"
issueiid=""
projectpath=""
>
<div>
<div
class="d-flex justify-content-between align-items-center w-100"
>
<designversiondropdown-stub
all-versions=""
/>
<!---->
</div>
</header>
......@@ -13,11 +21,17 @@ exports[`Design management upload form component hides button if cant upload 1`]
exports[`Design management upload form component renders upload design button 1`] = `
<header
class="row-content-block border-top-0 p-2 d-flex"
issueiid=""
projectpath=""
>
<div>
<uploadbutton-stub
isinverted="true"
<div
class="d-flex justify-content-between align-items-center w-100"
>
<designversiondropdown-stub
all-versions=""
/>
<uploadbutton-stub />
</div>
</header>
`;
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueRouter from 'vue-router';
import DesignVersionDropdown from 'ee/design_management/components/upload/design_version_dropdown.vue';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import mockAllVersions from './mock_data/all_versions';
const VERSION_ID = 3;
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter();
describe('Design management design version dropdown component', () => {
let wrapper;
function createComponent() {
wrapper = shallowMount(DesignVersionDropdown, {
propsData: {
projectPath: '',
issueIid: '',
allVersions: mockAllVersions,
},
localVue,
router,
});
}
afterEach(() => {
wrapper.destroy();
});
const findVersionLink = index => wrapper.findAll('.js-version-link').at(index);
it('renders design version dropdown button', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
it('renders design version list', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
describe('selected version name', () => {
it('has "latest" on most recent version item', () => {
createComponent();
expect(findVersionLink(0).text()).toContain('latest');
});
});
describe('versions list', () => {
it('pushes version id when a version is clicked', () => {
createComponent();
wrapper.vm.$router.push(`/designs?version=${VERSION_ID}`);
const CurrentVersionNumber = wrapper.vm.getCurrentVersionNumber();
expect(wrapper.find(GlDropdown).attributes('text')).toBe(
`Showing Version #${CurrentVersionNumber}`,
);
});
it('should have the same length as apollo query', () => {
createComponent();
expect(wrapper.findAll(GlDropdownItem).length).toEqual(wrapper.vm.allVersions.length);
});
});
});
......@@ -9,6 +9,9 @@ describe('Design management upload form component', () => {
propsData: {
isSaving,
canUploadDesign,
projectPath: '',
issueIid: '',
allVersions: [],
},
});
}
......
export default [
{
node: {
id: 'gid://gitlab/DesignManagement::Version/3',
sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
},
},
{
node: {
id: 'gid://gitlab/DesignManagement::Version/2',
sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
},
},
];
......@@ -3,6 +3,7 @@
exports[`Design management index page designs renders designs list 1`] = `
<div>
<uploadform-stub
all-versions=""
canuploaddesign="true"
/>
......
import { shallowMount } from '@vue/test-utils';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueRouter from 'vue-router';
import Index from 'ee/design_management/pages/index.vue';
import UploadForm from 'ee/design_management/components/upload/form.vue';
import uploadDesignQuery from 'ee/design_management/queries/uploadDesign.graphql';
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter();
describe('Design management index page', () => {
let mutate;
let vm;
......@@ -24,6 +29,8 @@ describe('Design management index page', () => {
vm = shallowMount(Index, {
mocks: { $apollo },
stubs: ['router-view'],
localVue,
router,
});
vm.setData({
......@@ -112,6 +119,17 @@ describe('Design management index page', () => {
id: expect.anything(),
image: '',
filename: 'test',
versions: {
__typename: 'DesignVersionConnection',
edges: {
__typename: 'DesignVersionEdge',
node: {
__typename: 'DesignVersion',
id: expect.anything(),
sha: expect.anything(),
},
},
},
},
],
},
......
......@@ -4543,6 +4543,9 @@ msgstr ""
msgid "DesignManagement|%{current_design} of %{designs_count}"
msgstr ""
msgid "DesignManagement|Add designs"
msgstr ""
msgid "DesignManagement|Could not find design, please try again."
msgstr ""
......@@ -4567,9 +4570,6 @@ msgstr ""
msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
msgstr ""
msgid "DesignManagement|Upload designs"
msgstr ""
msgid "Designs"
msgstr ""
......@@ -12694,6 +12694,12 @@ msgid_plural "Showing %d events"
msgstr[0] ""
msgstr[1] ""
msgid "Showing Latest Version"
msgstr ""
msgid "Showing Version #%{versionNumber}"
msgstr ""
msgid "Showing all issues"
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