Commit 15a86e9d authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '13490-design-view-needs-more-information-pt-1' into 'master'

Extend Design view sidebar with issue link and a list of participants

See merge request gitlab-org/gitlab!22103
parents 2a58cfc3 ec2ff7cb
...@@ -28,6 +28,11 @@ export default { ...@@ -28,6 +28,11 @@ export default {
required: false, required: false,
default: 7, default: 7,
}, },
showParticipantLabel: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
return { return {
...@@ -80,6 +85,7 @@ export default { ...@@ -80,6 +85,7 @@ export default {
<template> <template>
<div> <div>
<div <div
v-if="showParticipantLabel"
v-tooltip v-tooltip
:title="participantLabel" :title="participantLabel"
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
...@@ -92,7 +98,7 @@ export default { ...@@ -92,7 +98,7 @@ export default {
<gl-loading-icon v-if="loading" class="js-participants-collapsed-loading-icon" /> <gl-loading-icon v-if="loading" class="js-participants-collapsed-loading-icon" />
<span v-else class="js-participants-collapsed-count"> {{ participantCount }} </span> <span v-else class="js-participants-collapsed-count"> {{ participantCount }} </span>
</div> </div>
<div class="title hide-collapsed"> <div v-if="showParticipantLabel" class="title hide-collapsed">
<gl-loading-icon <gl-loading-icon
v-if="loading" v-if="loading"
:inline="true" :inline="true"
......
---
title: Extend Design view sidebar with issue link and a list of participants
merge_request: 22103
author:
type: added
...@@ -105,7 +105,7 @@ export default { ...@@ -105,7 +105,7 @@ export default {
<icon :size="18" name="close" /> <icon :size="18" name="close" />
</router-link> </router-link>
<div class="overflow-hidden d-flex align-items-center"> <div class="overflow-hidden d-flex align-items-center">
<h2 class="m-0 str-truncated-100">{{ filename }}</h2> <h2 class="m-0 str-truncated-100 gl-font-size-14">{{ filename }}</h2>
<small v-if="updatedAt" class="text-secondary">{{ updatedText }}</small> <small v-if="updatedAt" class="text-secondary">{{ updatedText }}</small>
</div> </div>
<pagination :id="id" class="ml-auto flex-shrink-0" /> <pagination :id="id" class="ml-auto flex-shrink-0" />
......
fragment Author on User {
avatarUrl
name
username
webUrl
}
\ No newline at end of file
#import "./diffRefs.fragment.graphql" #import "./diffRefs.fragment.graphql"
#import "./author.fragment.graphql"
fragment DesignNote on Note { fragment DesignNote on Note {
id id
author { author {
avatarUrl ...Author
name
username
webUrl
} }
body body
bodyHtml bodyHtml
......
#import "../fragments/design.fragment.graphql" #import "../fragments/design.fragment.graphql"
#import "../fragments/author.fragment.graphql"
query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [String!]) { query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [String!]) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {
...@@ -9,6 +10,18 @@ query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [Stri ...@@ -9,6 +10,18 @@ query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [Stri
edges { edges {
node { node {
...DesignItem ...DesignItem
issue {
title
webPath
webUrl
participants {
edges {
node {
...Author
}
}
}
}
} }
} }
} }
......
...@@ -10,10 +10,15 @@ import DesignOverlay from '../../components/design_overlay.vue'; ...@@ -10,10 +10,15 @@ import DesignOverlay from '../../components/design_overlay.vue';
import DesignDiscussion from '../../components/design_notes/design_discussion.vue'; import DesignDiscussion from '../../components/design_notes/design_discussion.vue';
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue'; import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
import DesignDestroyer from '../../components/design_destroyer.vue'; import DesignDestroyer from '../../components/design_destroyer.vue';
import Participants from '~/sidebar/components/participants/participants.vue';
import getDesignQuery from '../../graphql/queries/getDesign.query.graphql'; import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
import appDataQuery from '../../graphql/queries/appData.query.graphql'; import appDataQuery from '../../graphql/queries/appData.query.graphql';
import createImageDiffNoteMutation from '../../graphql/mutations/createImageDiffNote.mutation.graphql'; import createImageDiffNoteMutation from '../../graphql/mutations/createImageDiffNote.mutation.graphql';
import { extractDiscussions, extractDesign } from '../../utils/design_management_utils'; import {
extractDiscussions,
extractDesign,
extractParticipants,
} from '../../utils/design_management_utils';
import { updateStoreAfterAddImageDiffNote } from '../../utils/cache_update'; import { updateStoreAfterAddImageDiffNote } from '../../utils/cache_update';
import { import {
ADD_DISCUSSION_COMMENT_ERROR, ADD_DISCUSSION_COMMENT_ERROR,
...@@ -33,6 +38,7 @@ export default { ...@@ -33,6 +38,7 @@ export default {
DesignReplyForm, DesignReplyForm,
GlLoadingIcon, GlLoadingIcon,
GlAlert, GlAlert,
Participants,
}, },
mixins: [allVersionsMixin], mixins: [allVersionsMixin],
props: { props: {
...@@ -51,8 +57,8 @@ export default { ...@@ -51,8 +57,8 @@ export default {
height: 0, height: 0,
}, },
projectPath: '', projectPath: '',
issueId: '',
errorMessage: '', errorMessage: '',
issueIid: '',
}; };
}, },
apollo: { apollo: {
...@@ -94,6 +100,9 @@ export default { ...@@ -94,6 +100,9 @@ export default {
discussionStartingNotes() { discussionStartingNotes() {
return this.discussions.map(discussion => discussion.notes[0]); return this.discussions.map(discussion => discussion.notes[0]);
}, },
discussionParticipants() {
return extractParticipants(this.design.issue.participants);
},
markdownPreviewPath() { markdownPreviewPath() {
return `/${this.projectPath}/preview_markdown?target_type=Issue`; return `/${this.projectPath}/preview_markdown?target_type=Issue`;
}, },
...@@ -130,6 +139,12 @@ export default { ...@@ -130,6 +139,12 @@ export default {
}, },
}; };
}, },
issue() {
return {
...this.design.issue,
webPath: this.design.issue.webPath.substr(1),
};
},
}, },
mounted() { mounted() {
Mousetrap.bind('esc', this.closeDesign); Mousetrap.bind('esc', this.closeDesign);
...@@ -243,6 +258,15 @@ export default { ...@@ -243,6 +258,15 @@ export default {
</div> </div>
</div> </div>
<div class="image-notes"> <div class="image-notes">
<h2 class="gl-font-size-20 font-weight-bold mt-0">{{ issue.title }}</h2>
<a class="text-tertiary text-decoration-none mb-3 d-block" :href="issue.webUrl">{{
issue.webPath
}}</a>
<participants
:participants="discussionParticipants"
:show-participant-label="false"
class="mb-4"
/>
<template v-if="renderDiscussions"> <template v-if="renderDiscussions">
<design-discussion <design-discussion
v-for="(discussion, index) in discussions" v-for="(discussion, index) in discussions"
...@@ -274,7 +298,7 @@ export default { ...@@ -274,7 +298,7 @@ export default {
/> />
</apollo-mutation> </apollo-mutation>
</template> </template>
<h2 v-else class="new-discussion-disclaimer m-0"> <h2 v-else class="new-discussion-disclaimer gl-font-size-14 m-0">
{{ __("Click the image where you'd like to start a new discussion") }} {{ __("Click the image where you'd like to start a new discussion") }}
</h2> </h2>
</div> </div>
......
...@@ -62,6 +62,23 @@ const addDiscussionCommentToStore = (store, createNote, query, queryVariables, d ...@@ -62,6 +62,23 @@ const addDiscussionCommentToStore = (store, createNote, query, queryVariables, d
]; ];
design.notesCount += 1; 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: {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
__typename: 'User',
...createNote.note.author,
},
},
];
}
store.writeQuery({ store.writeQuery({
query, query,
variables: queryVariables, variables: queryVariables,
...@@ -101,6 +118,23 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) = ...@@ -101,6 +118,23 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) =
const design = extractDesign(data); const design = extractDesign(data);
const notesCount = design.notesCount + 1; const notesCount = design.notesCount + 1;
design.discussions.edges = [...design.discussions.edges, newDiscussion]; design.discussions.edges = [...design.discussions.edges, 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: {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
__typename: 'User',
...createImageDiffNote.note.author,
},
},
];
}
store.writeQuery({ store.writeQuery({
query, query,
variables, variables,
......
...@@ -86,3 +86,11 @@ export const designUploadOptimisticResponse = files => { ...@@ -86,3 +86,11 @@ export const designUploadOptimisticResponse = files => {
}, },
}; };
}; };
const normalizeAuthor = author => ({
...author,
web_url: author.webUrl,
avatar_url: author.avatarUrl,
});
export const extractParticipants = users => users.edges.map(({ node }) => normalizeAuthor(node));
...@@ -4,10 +4,6 @@ ...@@ -4,10 +4,6 @@
.with-performance-bar & { .with-performance-bar & {
top: 35px; top: 35px;
} }
h2 {
font-size: 14px;
}
} }
.design-list-item { .design-list-item {
......
...@@ -18,7 +18,7 @@ exports[`Design management toolbar component renders design and updated data 1`] ...@@ -18,7 +18,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
class="overflow-hidden d-flex align-items-center" class="overflow-hidden d-flex align-items-center"
> >
<h2 <h2
class="m-0 str-truncated-100" class="m-0 str-truncated-100 gl-font-size-14"
> >
test.jpg test.jpg
</h2> </h2>
......
...@@ -7,6 +7,23 @@ export default { ...@@ -7,6 +7,23 @@ export default {
updatedBy: { updatedBy: {
name: 'test', 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: { discussions: {
edges: [ edges: [
{ {
...@@ -19,6 +36,12 @@ export default { ...@@ -19,6 +36,12 @@ export default {
node: { node: {
id: 'note-id', id: 'note-id',
body: '123', body: '123',
author: {
name: 'Administrator',
username: 'root',
webUrl: 'link-to-author',
avatarUrl: 'link-to-avatar',
},
}, },
}, },
], ],
......
...@@ -37,6 +37,25 @@ exports[`Design management design index page renders design index 1`] = ` ...@@ -37,6 +37,25 @@ exports[`Design management design index page renders design index 1`] = `
<div <div
class="image-notes" class="image-notes"
> >
<h2
class="gl-font-size-20 font-weight-bold mt-0"
>
My precious issue
</h2>
<a
class="text-tertiary text-decoration-none mb-3 d-block"
href="full-issue-url"
>
ull-issue-path
</a>
<participants-stub
class="mb-4"
numberoflessparticipants="7"
participants="[object Object]"
/>
<design-discussion-stub <design-discussion-stub
designid="1" designid="1"
discussion="[object Object]" discussion="[object Object]"
...@@ -114,7 +133,26 @@ exports[`Design management design index page with error GlAlert is rendered in c ...@@ -114,7 +133,26 @@ exports[`Design management design index page with error GlAlert is rendered in c
class="image-notes" class="image-notes"
> >
<h2 <h2
class="new-discussion-disclaimer m-0" class="gl-font-size-20 font-weight-bold mt-0"
>
My precious issue
</h2>
<a
class="text-tertiary text-decoration-none mb-3 d-block"
href="full-issue-url"
>
ull-issue-path
</a>
<participants-stub
class="mb-4"
numberoflessparticipants="7"
participants="[object Object]"
/>
<h2
class="new-discussion-disclaimer gl-font-size-14 m-0"
> >
Click the image where you'd like to start a new discussion Click the image where you'd like to start a new discussion
......
...@@ -4,6 +4,7 @@ import { ApolloMutation } from 'vue-apollo'; ...@@ -4,6 +4,7 @@ import { ApolloMutation } from 'vue-apollo';
import DesignIndex from 'ee/design_management/pages/design/index.vue'; import DesignIndex from 'ee/design_management/pages/design/index.vue';
import DesignDiscussion from 'ee/design_management/components/design_notes/design_discussion.vue'; import DesignDiscussion from 'ee/design_management/components/design_notes/design_discussion.vue';
import DesignReplyForm from 'ee/design_management/components/design_notes/design_reply_form.vue'; import DesignReplyForm from 'ee/design_management/components/design_notes/design_reply_form.vue';
import Participants from '~/sidebar/components/participants/participants.vue';
import createImageDiffNoteMutation from 'ee/design_management/graphql/mutations/createImageDiffNote.mutation.graphql'; import createImageDiffNoteMutation from 'ee/design_management/graphql/mutations/createImageDiffNote.mutation.graphql';
import design from '../../mock_data/design'; import design from '../../mock_data/design';
...@@ -44,6 +45,7 @@ describe('Design management design index page', () => { ...@@ -44,6 +45,7 @@ describe('Design management design index page', () => {
const findDiscussions = () => wrapper.findAll(DesignDiscussion); const findDiscussions = () => wrapper.findAll(DesignDiscussion);
const findDiscussionForm = () => wrapper.find(DesignReplyForm); const findDiscussionForm = () => wrapper.find(DesignReplyForm);
const findParticipants = () => wrapper.find(Participants);
function createComponent(loading = false) { function createComponent(loading = false) {
const $apollo = { const $apollo = {
...@@ -99,6 +101,22 @@ describe('Design management design index page', () => { ...@@ -99,6 +101,22 @@ describe('Design management design index page', () => {
}); });
}); });
it('renders participants', () => {
setDesign();
wrapper.setData({
design,
});
return wrapper.vm.$nextTick().then(() => {
expect(findParticipants().exists()).toBe(true);
});
});
it('passes the correct amount of participants to the Participants component', () => {
expect(findParticipants().props('participants').length).toBe(1);
});
describe('when has no discussions', () => { describe('when has no discussions', () => {
beforeEach(() => { beforeEach(() => {
setDesign(); setDesign();
......
...@@ -46,7 +46,7 @@ describe('extractDiscussions', () => { ...@@ -46,7 +46,7 @@ describe('extractDiscussions', () => {
}; };
}); });
it('discards the edges.node artefacts of GraphQL', () => { it('discards the edges.node artifacts of GraphQL', () => {
expect(extractDiscussions(discussions)).toEqual([ expect(extractDiscussions(discussions)).toEqual([
{ id: 1, notes: ['a'] }, { id: 1, notes: ['a'] },
{ id: 2, notes: ['b'] }, { id: 2, notes: ['b'] },
......
...@@ -182,4 +182,21 @@ describe('Participants', function() { ...@@ -182,4 +182,21 @@ describe('Participants', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSidebar'); expect(vm.$emit).toHaveBeenCalledWith('toggleSidebar');
}); });
}); });
describe('when not showing participants label', () => {
beforeEach(() => {
vm = mountComponent(Participants, {
participants: PARTICIPANT_LIST,
showParticipantLabel: false,
});
});
it('does not show sidebar collapsed icon', () => {
expect(vm.$el.querySelector('.sidebar-collapsed-icon')).not.toBeTruthy();
});
it('does not show participants label title', () => {
expect(vm.$el.querySelector('.title')).not.toBeTruthy();
});
});
}); });
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