diff options
Diffstat (limited to 'app/assets/javascripts/work_items/components/notes')
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> · <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> |