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/design_management/components')
-rw-r--r--app/assets/javascripts/design_management/components/design_description/description_form.vue9
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_discussion.vue6
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note.vue153
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note_awards_list.vue34
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue2
5 files changed, 192 insertions, 12 deletions
diff --git a/app/assets/javascripts/design_management/components/design_description/description_form.vue b/app/assets/javascripts/design_management/components/design_description/description_form.vue
index 890d7f80f8d..413442074f0 100644
--- a/app/assets/javascripts/design_management/components/design_description/description_form.vue
+++ b/app/assets/javascripts/design_management/components/design_description/description_form.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton, GlFormGroup, GlAlert, GlTooltipDirective } from '@gitlab/ui';
-
import SafeHtml from '~/vue_shared/directives/safe_html';
import { __, s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
@@ -9,7 +8,7 @@ import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import { toggleMarkCheckboxes } from '~/behaviors/markdown/utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-
+import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking';
import updateDesignDescriptionMutation from '../../graphql/mutations/update_design_description.mutation.graphql';
import { UPDATE_DESCRIPTION_ERROR } from '../../utils/error_messages';
@@ -110,6 +109,11 @@ export default {
async updateDesignDescription() {
this.isSubmitting = true;
+ if (this.$refs.markdownEditor) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ trackSavedUsingEditor(this.$refs.markdownEditor.isContentEditorActive, 'Design');
+ }
+
try {
const designDescriptionInput = { description: this.descriptionText, id: this.design.id };
@@ -165,6 +169,7 @@ export default {
</gl-alert>
</div>
<markdown-editor
+ ref="markdownEditor"
:value="descriptionText"
:render-markdown-path="markdownPreviewPath"
:markdown-docs-path="$options.markdownDocsPath"
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
index 5affd448419..45f33967476 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
@@ -292,7 +292,9 @@ export default {
<design-note-pin :is-resolved="discussion.resolved" :label="discussion.index" />
<ul
class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none"
+ :class="{ 'gl-bg-blue-50': isDiscussionActive }"
data-qa-selector="design_discussion_content"
+ data-testid="design-discussion-content"
>
<design-note
:note="firstNote"
@@ -300,7 +302,7 @@ export default {
:is-resolving="isResolving"
:is-discussion="true"
:noteable-id="noteableId"
- :class="{ 'gl-bg-blue-50': isDiscussionActive }"
+ :design-variables="designVariables"
@delete-note="showDeleteNoteConfirmationModal($event)"
>
<template v-if="isLoggedIn && discussion.resolvable" #resolve-discussion>
@@ -343,7 +345,7 @@ export default {
:is-resolving="isResolving"
:noteable-id="noteableId"
:is-discussion="false"
- :class="{ 'gl-bg-blue-50': isDiscussionActive }"
+ :design-variables="designVariables"
@delete-note="showDeleteNoteConfirmationModal($event)"
/>
<li
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index 0eac2cad68d..1f2c9f19a95 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -7,14 +7,21 @@ import {
GlLink,
GlTooltipDirective,
} from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import { produce } from 'immer';
import SafeHtml from '~/vue_shared/directives/safe_html';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPENAME_USER } from '~/graphql_shared/constants';
import { __ } from '~/locale';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import EmojiPicker from '~/emoji/components/picker.vue';
+import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import updateNoteMutation from '../../graphql/mutations/update_note.mutation.graphql';
+import designNoteAwardEmojiToggleMutation from '../../graphql/mutations/design_note_award_emoji_toggle.mutation.graphql';
import { hasErrors } from '../../utils/cache_update';
import { findNoteId, extractDesignNoteId } from '../../utils/design_management_utils';
+import DesignNoteAwardsList from './design_note_awards_list.vue';
import DesignReplyForm from './design_reply_form.vue';
export default {
@@ -24,7 +31,9 @@ export default {
deleteCommentText: __('Delete comment'),
},
components: {
+ DesignNoteAwardsList,
DesignReplyForm,
+ EmojiPicker,
GlAvatar,
GlAvatarLink,
GlButton,
@@ -37,6 +46,7 @@ export default {
GlTooltip: GlTooltipDirective,
SafeHtml,
},
+ inject: ['issueIid', 'projectPath'],
props: {
note: {
type: Object,
@@ -56,6 +66,10 @@ export default {
type: String,
required: true,
},
+ designVariables: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
@@ -64,6 +78,26 @@ export default {
};
},
computed: {
+ currentUserId() {
+ return window.gon.current_user_id;
+ },
+ currentUserFullName() {
+ return window.gon.current_user_fullname;
+ },
+ canAwardEmoji() {
+ return this.note.userPermissions.awardEmoji;
+ },
+ awards() {
+ return this.note.awardEmoji.nodes.map((award) => {
+ return {
+ ...award,
+ user: {
+ ...award.user,
+ id: getIdFromGraphQLId(award.user.id),
+ },
+ };
+ });
+ },
author() {
return this.note.author;
},
@@ -124,6 +158,93 @@ export default {
this.$emit('error', data.errors[0]);
}
},
+ isEmojiPresentForCurrentUser(name) {
+ return (
+ this.awards.findIndex(
+ (emoji) => emoji.name === name && emoji.user.id === this.currentUserId,
+ ) > -1
+ );
+ },
+ /**
+ * Prepare award emoji nodes based on emoji name
+ * and whether the user has toggled the emoji off or on
+ */
+ getAwardEmojiNodes(name, toggledOn) {
+ // If the emoji toggled on, add the emoji
+ if (toggledOn) {
+ // If emoji is already present in award list, no action is needed
+ if (this.isEmojiPresentForCurrentUser(name)) {
+ return this.note.awardEmoji.nodes;
+ }
+
+ // else make a copy of unmutable list and return the list after adding the new emoji
+ const awardEmojiNodes = [...this.note.awardEmoji.nodes];
+ awardEmojiNodes.push({
+ name,
+ __typename: 'AwardEmoji',
+ user: {
+ id: convertToGraphQLId(TYPENAME_USER, this.currentUserId),
+ name: this.currentUserFullName,
+ __typename: 'UserCore',
+ },
+ });
+
+ return awardEmojiNodes;
+ }
+
+ // else just filter the emoji
+ return this.note.awardEmoji.nodes.filter(
+ (emoji) =>
+ !(emoji.name === name && getIdFromGraphQLId(emoji.user.id) === this.currentUserId),
+ );
+ },
+ handleAwardEmoji(name) {
+ this.$apollo
+ .mutate({
+ mutation: designNoteAwardEmojiToggleMutation,
+ variables: {
+ name,
+ awardableId: this.note.id,
+ },
+ optimisticResponse: {
+ awardEmojiToggle: {
+ errors: [],
+ toggledOn: !this.isEmojiPresentForCurrentUser(name),
+ },
+ },
+ update: (
+ cache,
+ {
+ data: {
+ awardEmojiToggle: { toggledOn },
+ },
+ },
+ ) => {
+ const query = {
+ query: getDesignQuery,
+ variables: this.designVariables,
+ };
+
+ const sourceData = cache.readQuery(query);
+
+ const newData = produce(sourceData, (draftState) => {
+ const {
+ awardEmoji,
+ } = draftState.project.issue.designCollection.designs.nodes[0].discussions.nodes
+ .find((d) => d.id === this.note.discussion.id)
+ .notes.nodes.find((n) => n.id === this.note.id);
+
+ awardEmoji.nodes = this.getAwardEmojiNodes(name, toggledOn);
+ });
+
+ cache.writeQuery({ ...query, data: newData });
+ },
+ })
+ .catch((error) => {
+ Sentry.captureException(error);
+ this.$emit('error', error);
+ });
+ },
},
updateNoteMutation,
};
@@ -131,7 +252,12 @@ export default {
<template>
<timeline-entry-item :id="`note_${noteAnchorId}`" class="design-note note-form">
- <gl-avatar-link :href="author.webUrl" class="gl-float-left gl-mr-3">
+ <gl-avatar-link
+ :href="author.webUrl"
+ :data-user-id="authorId"
+ :data-username="author.username"
+ class="gl-float-left gl-mr-3 link-inherit-color js-user-link"
+ >
<gl-avatar :size="32" :src="author.avatarUrl" :entity-name="author.username" />
</gl-avatar-link>
@@ -140,7 +266,7 @@ export default {
<gl-link
v-once
:href="author.webUrl"
- class="js-user-link"
+ class="js-user-link link-inherit-color"
data-testid="user-link"
:data-user-id="authorId"
:data-username="author.username"
@@ -152,15 +278,23 @@ export default {
<span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<gl-link
- class="note-timestamp system-note-separator gl-display-block gl-mb-2 gl-font-sm"
+ class="note-timestamp system-note-separator gl-display-block gl-mb-2 gl-font-sm link-inherit-color"
:href="`#note_${noteAnchorId}`"
>
<time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" />
</gl-link>
</span>
</div>
- <div class="gl-display-flex gl-align-items-baseline gl-mt-n2 gl-mr-n2">
+ <div class="gl-display-flex gl-align-items-flex-start gl-mt-n2 gl-mr-n2">
<slot name="resolve-discussion"></slot>
+ <emoji-picker
+ v-if="canAwardEmoji"
+ toggle-class="note-action-button note-emoji-button btn-icon btn-default-tertiary"
+ boundary="viewport"
+ :right="false"
+ data-testid="note-emoji-button"
+ @click="handleAwardEmoji"
+ />
<gl-button
v-if="isEditingAndHasPermissions"
v-gl-tooltip
@@ -175,7 +309,6 @@ export default {
<gl-disclosure-dropdown
v-if="isEditingAndHasPermissions"
v-gl-tooltip.hover
- toggle-class="btn-sm"
icon="ellipsis_v"
category="tertiary"
data-qa-selector="design_discussion_actions_ellipsis_dropdown"
@@ -198,8 +331,14 @@ export default {
></div>
<slot name="resolved-status"></slot>
</template>
+ <design-note-awards-list
+ v-if="awards.length"
+ :awards="awards"
+ :can-award-emoji="note.userPermissions.awardEmoji"
+ @award="handleAwardEmoji"
+ />
<design-reply-form
- v-else
+ v-if="isEditing"
:markdown-preview-path="markdownPreviewPath"
:design-note-mutation="$options.updateNoteMutation"
:mutation-variables="mutationVariables"
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note_awards_list.vue b/app/assets/javascripts/design_management/components/design_notes/design_note_awards_list.vue
new file mode 100644
index 00000000000..f5456f47410
--- /dev/null
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note_awards_list.vue
@@ -0,0 +1,34 @@
+<script>
+import AwardsList from '~/vue_shared/components/awards_list.vue';
+
+export default {
+ components: {
+ AwardsList,
+ },
+ props: {
+ awards: {
+ type: Array,
+ required: true,
+ },
+ canAwardEmoji: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ currentUserId() {
+ return window.gon.current_user_id;
+ },
+ },
+};
+</script>
+
+<template>
+ <awards-list
+ :awards="awards"
+ :can-award-emoji="canAwardEmoji"
+ :current-user-id="currentUserId"
+ class="gl-px-2 gl-mt-5"
+ @award="$emit('award', $event)"
+ />
+</template>
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
index 7474f8f3298..764c78ff581 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
@@ -233,7 +233,7 @@ export default {
</template>
</markdown-field>
<slot name="resolve-checkbox"></slot>
- <div class="note-form-actions gl-display-flex">
+ <div class="note-form-actions gl-display-flex gl-mt-4!">
<gl-button
ref="submitButton"
:disabled="!hasValue"