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:
Diffstat (limited to 'app/assets/javascripts/work_items/components/notes')
-rw-r--r--app/assets/javascripts/work_items/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_note.vue21
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue64
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue95
4 files changed, 145 insertions, 37 deletions
diff --git a/app/assets/javascripts/work_items/components/notes/system_note.vue b/app/assets/javascripts/work_items/components/notes/system_note.vue
index 1fa217f456e..7903adea9bd 100644
--- a/app/assets/javascripts/work_items/components/notes/system_note.vue
+++ b/app/assets/javascripts/work_items/components/notes/system_note.vue
@@ -114,7 +114,7 @@ export default {
:note-id="noteId"
:is-system-note="true"
>
- <span ref="gfm-content" v-safe-html="actionTextHtml"></span>
+ <span ref="gfm-content" v-safe-html="actionTextHtml" class="gl-word-break-word"></span>
<template v-if="canSeeDescriptionVersion" #extra-controls>
&middot;
<gl-button
diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_note.vue
index 7ad424868c6..a2667a379e1 100644
--- a/app/assets/javascripts/work_items/components/notes/work_item_note.vue
+++ b/app/assets/javascripts/work_items/components/notes/work_item_note.vue
@@ -18,10 +18,12 @@ import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutati
import updateWorkItemNoteMutation from '../../graphql/notes/update_work_item_note.mutation.graphql';
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
import WorkItemCommentForm from './work_item_comment_form.vue';
+import WorkItemNoteAwardsList from './work_item_note_awards_list.vue';
export default {
name: 'WorkItemNoteThread',
components: {
+ WorkItemNoteAwardsList,
TimelineEntryItem,
NoteBody,
NoteHeader,
@@ -101,6 +103,9 @@ export default {
author() {
return this.note.author;
},
+ authorId() {
+ return getIdFromGraphQLId(this.author.id);
+ },
entryClass() {
return {
'note note-wrapper note-comment': true,
@@ -149,10 +154,10 @@ export default {
return window.gon.current_user_id;
},
isCurrentUserAuthorOfNote() {
- return getIdFromGraphQLId(this.author.id) === this.currentUserId;
+ return this.authorId === this.currentUserId;
},
isWorkItemAuthor() {
- return getIdFromGraphQLId(this.workItem?.author?.id) === getIdFromGraphQLId(this.author.id);
+ return getIdFromGraphQLId(this.workItem?.author?.id) === this.authorId;
},
projectName() {
return this.workItem?.project?.name;
@@ -284,7 +289,12 @@ export default {
<template>
<timeline-entry-item :id="noteAnchorId" :class="entryClass">
<div :key="note.id" class="timeline-avatar gl-float-left">
- <gl-avatar-link :href="author.webUrl">
+ <gl-avatar-link
+ :href="author.webUrl"
+ :data-user-id="authorId"
+ :data-username="author.username"
+ class="js-user-link"
+ >
<gl-avatar
:src="author.avatarUrl"
:entity-name="author.username"
@@ -323,6 +333,8 @@ export default {
<div class="gl-display-inline-flex">
<note-actions
:show-award-emoji="hasAwardEmojiPermission"
+ :work-item-iid="workItemIid"
+ :note="note"
:note-url="noteUrl"
:show-reply="showReply"
:show-edit="hasAdminPermission"
@@ -356,6 +368,9 @@ export default {
:class="isFirstNote ? 'gl-pl-3' : 'gl-pl-8'"
/>
</div>
+ <div class="note-awards" :class="isFirstNote ? '' : 'gl-pl-7'">
+ <work-item-note-awards-list :note="note" :work-item-iid="workItemIid" :is-modal="isModal" />
+ </div>
</div>
</timeline-entry-item>
</template>
diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue b/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue
index b32a8c78c93..e5da3d346ae 100644
--- a/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue
+++ b/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue
@@ -1,17 +1,15 @@
<script>
import {
GlButton,
- GlIcon,
GlTooltipDirective,
GlDisclosureDropdown,
GlDisclosureDropdownItem,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
-import { __, s__, sprintf } from '~/locale';
+import { __, sprintf } from '~/locale';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import ReplyButton from '~/notes/components/note_actions/reply_button.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import addAwardEmojiMutation from '../../graphql/notes/work_item_note_add_award_emoji.mutation.graphql';
+import { getMutation, optimisticAwardUpdate } from '../../notes/award_utils';
export default {
name: 'WorkItemNoteActions',
@@ -25,19 +23,26 @@ export default {
reportAbuseText: __('Report abuse to administrator'),
},
components: {
+ EmojiPicker: () => import('~/emoji/components/picker.vue'),
GlButton,
- GlIcon,
GlDisclosureDropdown,
GlDisclosureDropdownItem,
ReplyButton,
- EmojiPicker: () => import('~/emoji/components/picker.vue'),
UserAccessRoleBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [glFeatureFlagsMixin()],
+ inject: ['fullPath'],
props: {
+ workItemIid: {
+ type: String,
+ required: true,
+ },
+ note: {
+ type: Object,
+ required: true,
+ },
showReply: {
type: Boolean,
required: true,
@@ -126,24 +131,29 @@ export default {
methods: {
async setAwardEmoji(name) {
+ const { mutation, mutationName, errorMessage } = getMutation({ note: this.note, name });
+
try {
- const {
- data: {
- awardEmojiAdd: { errors = [] },
- },
- } = await this.$apollo.mutate({
- mutation: addAwardEmojiMutation,
+ await this.$apollo.mutate({
+ mutation,
variables: {
- awardableId: this.noteId,
+ awardableId: this.note.id,
name,
},
+ optimisticResponse: {
+ [mutationName]: {
+ errors: [],
+ },
+ },
+ update: optimisticAwardUpdate({
+ note: this.note,
+ name,
+ fullPath: this.fullPath,
+ workItemIid: this.workItemIid,
+ }),
});
-
- if (errors.length > 0) {
- throw new Error(errors[0].message);
- }
} catch (error) {
- this.$emit('error', s__('WorkItem|Failed to award emoji'));
+ this.$emit('error', errorMessage);
Sentry.captureException(error);
}
},
@@ -185,23 +195,11 @@ export default {
{{ __('Contributor') }}
</user-access-role-badge>
<emoji-picker
- v-if="showAwardEmoji && glFeatures.workItemsMvc2"
+ v-if="showAwardEmoji"
toggle-class="note-action-button note-emoji-button btn-icon btn-default-tertiary"
data-testid="note-emoji-button"
@click="setAwardEmoji"
- >
- <template #button-content>
- <gl-icon class="award-control-icon-neutral gl-button-icon gl-icon" name="slight-smile" />
- <gl-icon
- class="award-control-icon-positive gl-button-icon gl-icon gl-left-3!"
- name="smiley"
- />
- <gl-icon
- class="award-control-icon-super-positive gl-button-icon gl-icon gl-left-3!"
- name="smile"
- />
- </template>
- </emoji-picker>
+ />
<reply-button v-if="showReply" ref="replyButton" @startReplying="$emit('startReplying')" />
<gl-button
v-if="showEdit"
diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue b/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue
new file mode 100644
index 00000000000..3c30c204ab6
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue
@@ -0,0 +1,95 @@
+<script>
+import * as Sentry from '@sentry/browser';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import AwardsList from '~/vue_shared/components/awards_list.vue';
+import { getMutation, optimisticAwardUpdate } from '../../notes/award_utils';
+
+export default {
+ components: {
+ AwardsList,
+ },
+ inject: ['fullPath'],
+ props: {
+ workItemIid: {
+ type: String,
+ required: true,
+ },
+ note: {
+ type: Object,
+ required: true,
+ },
+ isModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ awardsListBoundary() {
+ return this.isModal ? '.modal-body' : '';
+ },
+ awards() {
+ return this.note.awardEmoji.nodes.map((award) => {
+ return {
+ ...award,
+ user: {
+ ...award.user,
+ id: getIdFromGraphQLId(award.user.id),
+ },
+ };
+ });
+ },
+ hasAwardEmojiPermission() {
+ return this.note.userPermissions.awardEmoji;
+ },
+ currentUserId() {
+ return window.gon.current_user_id;
+ },
+ },
+ methods: {
+ async handleAward(name) {
+ if (!this.hasAwardEmojiPermission) {
+ return;
+ }
+
+ const { mutation, mutationName, errorMessage } = getMutation({ note: this.note, name });
+
+ try {
+ await this.$apollo.mutate({
+ mutation,
+ variables: {
+ awardableId: this.note.id,
+ name,
+ },
+ optimisticResponse: {
+ [mutationName]: {
+ errors: [],
+ },
+ },
+ update: optimisticAwardUpdate({
+ note: this.note,
+ name,
+ fullPath: this.fullPath,
+ workItemIid: this.workItemIid,
+ }),
+ });
+ } catch (error) {
+ this.$emit('error', errorMessage);
+ Sentry.captureException(error);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <awards-list
+ v-if="awards.length"
+ :awards="awards"
+ :can-award-emoji="hasAwardEmojiPermission"
+ :current-user-id="currentUserId"
+ :boundary="awardsListBoundary"
+ class="gl-px-2"
+ @award="handleAward($event)"
+ />
+</template>