diff options
Diffstat (limited to 'app/assets/javascripts/notes/components')
18 files changed, 316 insertions, 206 deletions
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index bd5945a951b..bf35d5c3b25 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -14,7 +14,7 @@ import { slugifyWithUnderscore, } from '~/lib/utils/text_utility'; import { sprintf } from '~/locale'; -import markdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -25,8 +25,8 @@ import { COMMENT_FORM } from '../i18n'; import issuableStateMixin from '../mixins/issuable_state'; import CommentFieldLayout from './comment_field_layout.vue'; import CommentTypeDropdown from './comment_type_dropdown.vue'; -import discussionLockedWidget from './discussion_locked_widget.vue'; -import noteSignedOutWidget from './note_signed_out_widget.vue'; +import DiscussionLockedWidget from './discussion_locked_widget.vue'; +import NoteSignedOutWidget from './note_signed_out_widget.vue'; const { UNPROCESSABLE_ENTITY } = httpStatusCodes; @@ -34,9 +34,9 @@ export default { name: 'CommentForm', i18n: COMMENT_FORM, components: { - noteSignedOutWidget, - discussionLockedWidget, - markdownField, + NoteSignedOutWidget, + DiscussionLockedWidget, + MarkdownField, GlAlert, GlButton, TimelineEntryItem, @@ -214,11 +214,7 @@ export default { note: { noteable_type: this.noteableType, noteable_id: this.getNoteableData.id, - // Internal notes were identified as `confidential` - // before we decided to treat them as _internal_ - // so now until API is updated we need to use `confidential` - // in request payload. - confidential: this.noteIsInternal, + internal: this.noteIsInternal, note: this.note, }, merge_request_diff_head_sha: this.getNoteableData.diff_head_sha, diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue index 3cf47f42e0c..1b1923a90f7 100644 --- a/app/assets/javascripts/notes/components/diff_discussion_header.vue +++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue @@ -4,16 +4,16 @@ import { escape } from 'lodash'; import { mapActions } from 'vuex'; import { truncateSha } from '~/lib/utils/text_utility'; import { s__, __, sprintf } from '~/locale'; -import noteEditedText from './note_edited_text.vue'; -import noteHeader from './note_header.vue'; +import NoteEditedText from './note_edited_text.vue'; +import NoteHeader from './note_header.vue'; export default { name: 'DiffDiscussionHeader', components: { GlAvatar, GlAvatarLink, - noteEditedText, - noteHeader, + NoteEditedText, + NoteHeader, }, directives: { SafeHtml, diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue index 6f0745d4fb0..dcbf4a0e5d3 100644 --- a/app/assets/javascripts/notes/components/discussion_actions.vue +++ b/app/assets/javascripts/notes/components/discussion_actions.vue @@ -59,6 +59,7 @@ export default { <resolve-discussion-button v-if="discussion.resolvable" data-qa-selector="resolve_discussion_button" + data-testid="resolve-discussion-button" :is-resolving="isResolving" :button-title="resolveButtonTitle" @onClick="$emit('resolve')" diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index eedcb0c09d4..6521b86edbb 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -1,7 +1,16 @@ <script> -import { GlTooltipDirective, GlButton, GlButtonGroup } from '@gitlab/ui'; +import { + GlTooltipDirective, + GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlIcon, +} from '@gitlab/ui'; import { mapGetters, mapActions } from 'vuex'; +import { throttle } from 'lodash'; import { __ } from '~/locale'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import discussionNavigation from '../mixins/discussion_navigation'; export default { @@ -11,14 +20,23 @@ export default { components: { GlButton, GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlIcon, }, - mixins: [discussionNavigation], + mixins: [glFeatureFlagsMixin(), discussionNavigation], props: { blocksMerge: { type: Boolean, required: true, }, }, + data() { + return { + jumpNext: throttle(this.jumpToNextDiscussion, 500), + jumpPrevious: throttle(this.jumpToPreviousDiscussion, 500), + }; + }, computed: { ...mapGetters([ 'getNoteableData', @@ -54,27 +72,44 @@ export default { <template> <div v-if="resolvableDiscussionsCount > 0" + id="discussionCounter" ref="discussionCounter" class="gl-display-flex discussions-counter" > <div - class="gl-display-flex gl-align-items-center gl-pl-4 gl-rounded-base gl-mr-3" + class="gl-display-flex gl-align-items-center gl-pl-4 gl-rounded-base gl-mr-3 gl-min-h-7" :class="{ 'gl-bg-orange-50': blocksMerge && !allResolved, 'gl-bg-gray-50': !blocksMerge || allResolved, - 'gl-pr-4': allResolved, 'gl-pr-2': !allResolved, }" data-testid="discussions-counter-text" > <template v-if="allResolved"> {{ __('All threads resolved!') }} + <gl-dropdown + size="small" + category="tertiary" + right + toggle-class="btn-icon" + class="gl-pt-0! gl-px-2 gl-h-full gl-ml-2" + > + <template #button-content> + <gl-icon name="ellipsis_v" class="mr-0" /> + </template> + <gl-dropdown-item + data-testid="toggle-all-discussions-btn" + @click="handleExpandDiscussions" + > + {{ toggleThreadsLabel }} + </gl-dropdown-item> + </gl-dropdown> </template> <template v-else> {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }} <gl-button-group class="gl-ml-3"> <gl-button - v-gl-tooltip.hover + v-gl-tooltip:discussionCounter.hover.bottom :title="__('Go to previous unresolved thread')" :aria-label="__('Go to previous unresolved thread')" class="discussion-previous-btn gl-rounded-base! gl-px-2!" @@ -83,10 +118,10 @@ export default { data-track-property="click_previous_unresolved_thread_top" icon="chevron-lg-up" category="tertiary" - @click="jumpToPreviousDiscussion" + @click="jumpPrevious" /> <gl-button - v-gl-tooltip.hover + v-gl-tooltip:discussionCounter.hover.bottom :title="__('Go to next unresolved thread')" :aria-label="__('Go to next unresolved thread')" class="discussion-next-btn gl-rounded-base! gl-px-2!" @@ -95,29 +130,33 @@ export default { data-track-property="click_next_unresolved_thread_top" icon="chevron-lg-down" category="tertiary" - @click="jumpToNextDiscussion" + @click="jumpNext" /> + <gl-dropdown + size="small" + category="tertiary" + right + toggle-class="btn-icon" + class="gl-pt-0! gl-px-2" + > + <template #button-content> + <gl-icon name="ellipsis_v" class="mr-0" /> + </template> + <gl-dropdown-item + data-testid="toggle-all-discussions-btn" + @click="handleExpandDiscussions" + > + {{ toggleThreadsLabel }} + </gl-dropdown-item> + <gl-dropdown-item + v-if="resolveAllDiscussionsIssuePath && !allResolved" + :href="resolveAllDiscussionsIssuePath" + > + {{ __('Create issue to resolve all threads') }} + </gl-dropdown-item> + </gl-dropdown> </gl-button-group> </template> </div> - <gl-button-group> - <gl-button - v-gl-tooltip - :title="toggleThreadsLabel" - :aria-label="toggleThreadsLabel" - class="toggle-all-discussions-btn" - :icon="allExpanded ? 'collapse' : 'expand'" - @click="handleExpandDiscussions" - /> - <gl-button - v-if="resolveAllDiscussionsIssuePath && !allResolved" - v-gl-tooltip - :href="resolveAllDiscussionsIssuePath" - :title="__('Create issue to resolve all threads')" - :aria-label="__('Create issue to resolve all threads')" - class="new-issue-for-discussion discussion-create-issue-btn" - icon="issue-new" - /> - </gl-button-group> </div> </template> diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index 15887c2738d..8a42fb6bd85 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -2,6 +2,9 @@ import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui'; import { mapGetters, mapActions } from 'vuex'; import { getLocationHash, doesHashExistInUrl } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; +import Tracking from '~/tracking'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE, @@ -9,15 +12,25 @@ import { DISCUSSION_TAB_LABEL, DISCUSSION_FILTER_TYPES, NOTE_UNDERSCORE, + ASC, + DESC, } from '../constants'; import notesEventHub from '../event_hub'; +const SORT_OPTIONS = [ + { key: DESC, text: __('Newest first'), cls: 'js-newest-first' }, + { key: ASC, text: __('Oldest first'), cls: 'js-oldest-first' }, +]; + export default { + SORT_OPTIONS, components: { GlDropdown, GlDropdownItem, GlDropdownDivider, + LocalStorageSync, }, + mixins: [Tracking.mixin()], props: { filters: { type: Array, @@ -39,11 +52,24 @@ export default { }; }, computed: { - ...mapGetters(['getNotesDataByProp', 'timelineEnabled', 'isLoading']), + ...mapGetters([ + 'getNotesDataByProp', + 'timelineEnabled', + 'isLoading', + 'sortDirection', + 'persistSortOrder', + 'noteableType', + ]), currentFilter() { if (!this.currentValue) return this.filters[0]; return this.filters.find((filter) => filter.value === this.currentValue); }, + selectedSortOption() { + return SORT_OPTIONS.find(({ key }) => this.sortDirection === key); + }, + sortStorageKey() { + return `sort_direction_${this.noteableType.toLowerCase()}`; + }, }, created() { if (window.mrTabs) { @@ -69,6 +95,7 @@ export default { 'setCommentsDisabled', 'setTargetNoteHash', 'setTimelineView', + 'setDiscussionSortDirection', ]), selectFilter(value, persistFilter = true) { const filter = parseInt(value, 10); @@ -108,31 +135,73 @@ export default { } return DISCUSSION_FILTER_TYPES.HISTORY; }, + fetchSortedDiscussions(direction) { + if (this.isSortDropdownItemActive(direction)) { + return; + } + + this.setDiscussionSortDirection({ direction }); + this.track('change_discussion_sort_direction', { property: direction }); + }, + isSortDropdownItemActive(sortDir) { + return sortDir === this.sortDirection; + }, }, }; </script> <template> - <gl-dropdown + <div v-if="displayFilters" - id="discussion-filter-dropdown" - class="full-width-mobile discussion-filter-container js-discussion-filter-container" - data-qa-selector="discussion_filter_dropdown" - :text="currentFilter.title" - :disabled="isLoading" + id="discussion-preferences" + data-testid="discussion-preferences" + class="gl-display-inline-block gl-vertical-align-bottom full-width-mobile" > - <div v-for="filter in filters" :key="filter.value" class="dropdown-item-wrapper"> - <gl-dropdown-item - :is-check-item="true" - :is-checked="filter.value === currentValue" - :class="{ 'is-active': filter.value === currentValue }" - :data-filter-type="filterType(filter.value)" - data-qa-selector="filter_menu_item" - @click.prevent="selectFilter(filter.value)" + <local-storage-sync + :value="sortDirection" + :storage-key="sortStorageKey" + :persist="persistSortOrder" + as-string + @input="setDiscussionSortDirection({ direction: $event })" + /> + <gl-dropdown + id="discussion-preferences-dropdown" + class="full-width-mobile" + data-qa-selector="discussion_preferences_dropdown" + text="Sort or filter" + :disabled="isLoading" + right + > + <div id="discussion-sort"> + <gl-dropdown-item + v-for="{ text, key, cls } in $options.SORT_OPTIONS" + :key="text" + :class="cls" + is-check-item + :is-checked="isSortDropdownItemActive(key)" + @click="fetchSortedDiscussions(key)" + > + {{ text }} + </gl-dropdown-item> + </div> + <gl-dropdown-divider /> + <div + id="discussion-filter" + class="discussion-filter-container js-discussion-filter-container" > - {{ filter.title }} - </gl-dropdown-item> - <gl-dropdown-divider v-if="filter.value === defaultValue" /> - </div> - </gl-dropdown> + <gl-dropdown-item + v-for="filter in filters" + :key="filter.value" + is-check-item + :is-checked="filter.value === currentValue" + :class="{ 'is-active': filter.value === currentValue }" + :data-filter-type="filterType(filter.value)" + data-qa-selector="filter_menu_item" + @click.prevent="selectFilter(filter.value)" + > + {{ filter.title }} + </gl-dropdown-item> + </div> + </gl-dropdown> + </div> </template> diff --git a/app/assets/javascripts/notes/components/discussion_navigator.vue b/app/assets/javascripts/notes/components/discussion_navigator.vue index c1e39f31bbb..03bdc7a2cc6 100644 --- a/app/assets/javascripts/notes/components/discussion_navigator.vue +++ b/app/assets/javascripts/notes/components/discussion_navigator.vue @@ -1,6 +1,7 @@ <script> /* global Mousetrap */ import 'mousetrap'; +import { throttle } from 'lodash'; import { keysFor, MR_NEXT_UNRESOLVED_DISCUSSION, @@ -11,12 +12,18 @@ import discussionNavigation from '~/notes/mixins/discussion_navigation'; export default { mixins: [discussionNavigation], + data() { + return { + jumpToNext: throttle(() => this.jumpToNextDiscussion({ behavior: 'auto' }), 200), + jumpToPrevious: throttle(() => this.jumpToPreviousDiscussion({ behavior: 'auto' }), 200), + }; + }, created() { eventHub.$on('jumpToFirstUnresolvedDiscussion', this.jumpToFirstUnresolvedDiscussion); }, mounted() { - Mousetrap.bind(keysFor(MR_NEXT_UNRESOLVED_DISCUSSION), this.jumpToNextDiscussion); - Mousetrap.bind(keysFor(MR_PREVIOUS_UNRESOLVED_DISCUSSION), this.jumpToPreviousDiscussion); + Mousetrap.bind(keysFor(MR_NEXT_UNRESOLVED_DISCUSSION), this.jumpToNext); + Mousetrap.bind(keysFor(MR_PREVIOUS_UNRESOLVED_DISCUSSION), this.jumpToPrevious); }, beforeDestroy() { Mousetrap.unbind(keysFor(MR_NEXT_UNRESOLVED_DISCUSSION)); diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index c7f293a219a..9806f8e5dc2 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -1,6 +1,6 @@ <script> import { GlTooltipDirective, GlIcon, GlButton, GlDropdownItem } from '@gitlab/ui'; -import { mapActions, mapGetters } from 'vuex'; +import { mapActions, mapGetters, mapState } from 'vuex'; import Api from '~/api'; import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status'; import createFlash from '~/flash'; @@ -11,6 +11,7 @@ import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge. import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { splitCamelCase } from '~/lib/utils/text_utility'; import ReplyButton from './note_actions/reply_button.vue'; +import TimelineEventButton from './note_actions/timeline_event_button.vue'; export default { i18n: { @@ -23,6 +24,7 @@ export default { components: { GlIcon, ReplyButton, + TimelineEventButton, GlButton, GlDropdownItem, UserAccessRoleBadge, @@ -133,7 +135,8 @@ export default { }, }, computed: { - ...mapGetters(['getUserDataByProp', 'getNoteableData']), + ...mapState(['isPromoteCommentToTimelineEventInProgress']), + ...mapGetters(['getUserDataByProp', 'getNoteableData', 'canUserAddIncidentTimelineEvents']), shouldShowActionsDropdown() { return this.currentUserId && (this.canEdit || this.canReportAsAbuse); }, @@ -199,7 +202,7 @@ export default { }, }, methods: { - ...mapActions(['toggleAwardRequest']), + ...mapActions(['toggleAwardRequest', 'promoteCommentToTimelineEvent']), onEdit() { this.$emit('handleEdit'); }, @@ -292,6 +295,12 @@ export default { class="line-resolve-btn note-action-button" @click="onResolve" /> + <timeline-event-button + v-if="canUserAddIncidentTimelineEvents" + :note-id="noteId" + :is-promotion-in-progress="isPromoteCommentToTimelineEventInProgress" + @click-promote-comment-to-event="promoteCommentToTimelineEvent" + /> <emoji-picker v-if="canAwardEmoji" toggle-class="note-action-button note-emoji-button btn-icon btn-default-tertiary" diff --git a/app/assets/javascripts/notes/components/note_actions/timeline_event_button.vue b/app/assets/javascripts/notes/components/note_actions/timeline_event_button.vue new file mode 100644 index 00000000000..4dd0c968282 --- /dev/null +++ b/app/assets/javascripts/notes/components/note_actions/timeline_event_button.vue @@ -0,0 +1,49 @@ +<script> +import { GlTooltipDirective, GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + i18n: { + buttonText: __('Add comment to incident timeline'), + addError: __('Error promoting the note to timeline event: %{error}'), + addGenericError: __('Something went wrong while promoting the note to timeline event.'), + }, + components: { + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + noteId: { + type: [String, Number], + required: true, + }, + isPromotionInProgress: { + type: Boolean, + required: false, + default: false, + }, + }, + methods: { + handleButtonClick() { + this.$emit('click-promote-comment-to-event', { + noteId: this.noteId, + addError: this.$options.i18n.addError, + addGenericError: this.$options.i18n.addGenericError, + }); + }, + }, +}; +</script> +<template> + <span v-gl-tooltip :title="$options.i18n.buttonText"> + <gl-button + category="tertiary" + icon="clock" + :aria-label="$options.i18n.buttonText" + :disabled="isPromotionInProgress" + @click="handleButtonClick" + /> + </span> +</template> diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index f1c41eea428..82c125b79ce 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -8,17 +8,17 @@ import { __ } from '~/locale'; import '~/behaviors/markdown/render_gfm'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; import autosave from '../mixins/autosave'; -import noteAttachment from './note_attachment.vue'; -import noteAwardsList from './note_awards_list.vue'; -import noteEditedText from './note_edited_text.vue'; -import noteForm from './note_form.vue'; +import NoteAttachment from './note_attachment.vue'; +import NoteAwardsList from './note_awards_list.vue'; +import NoteEditedText from './note_edited_text.vue'; +import NoteForm from './note_form.vue'; export default { components: { - noteEditedText, - noteAwardsList, - noteAttachment, - noteForm, + NoteEditedText, + NoteAwardsList, + NoteAttachment, + NoteForm, Suggestions, }, directives: { @@ -71,7 +71,7 @@ export default { return this.note.note; }, saveButtonTitle() { - return this.note.confidential ? __('Save internal note') : __('Save comment'); + return this.note.internal ? __('Save internal note') : __('Save comment'); }, hasSuggestion() { return this.note.suggestions && this.note.suggestions.length; diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue index 03cbdf45ddd..e0c3ed0c67a 100644 --- a/app/assets/javascripts/notes/components/note_edited_text.vue +++ b/app/assets/javascripts/notes/components/note_edited_text.vue @@ -1,11 +1,11 @@ <script> /* eslint-disable @gitlab/vue-require-i18n-strings */ -import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; export default { name: 'EditedNoteText', components: { - timeAgoTooltip, + TimeAgoTooltip, }, props: { actionText: { diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 30579a8eb0d..b6ede10d02b 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -4,7 +4,7 @@ import { mapGetters, mapActions, mapState } from 'vuex'; import { getDraft, updateDraft } from '~/lib/utils/autosave'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; -import markdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import eventHub from '../event_hub'; import issuableStateMixin from '../mixins/issuable_state'; import resolvable from '../mixins/resolvable'; @@ -15,7 +15,7 @@ export default { i18n: COMMENT_FORM, name: 'NoteForm', components: { - markdownField, + MarkdownField, CommentFieldLayout, GlButton, GlSprintf, @@ -136,7 +136,7 @@ export default { ); }, textareaPlaceholder() { - return this.discussionNote?.confidential + return this.discussionNote?.internal ? this.$options.i18n.bodyPlaceholderInternal : this.$options.i18n.bodyPlaceholder; }, @@ -331,7 +331,7 @@ export default { <form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form"> <comment-field-layout :noteable-data="getNoteableData" - :is-internal-note="discussion.confidential" + :is-internal-note="discussion.internal" > <markdown-field :markdown-preview-path="markdownPreviewPath" @@ -423,7 +423,7 @@ export default { category="primary" variant="confirm" data-qa-selector="reply_comment_button" - class="gl-mr-3 js-vue-issue-save js-comment-button" + class="gl-sm-mr-3 gl-xs-mb-3 js-vue-issue-save js-comment-button" @click="handleUpdate()" > {{ saveButtonTitle }} @@ -432,7 +432,7 @@ export default { v-if="discussion.resolvable" category="secondary" variant="default" - class="gl-mr-3 js-comment-resolve-button" + class="gl-sm-mr-3 gl-xs-mb-3 js-comment-resolve-button" @click.prevent="handleUpdate(true)" > {{ resolveButtonTitle }} diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue index 9917249f0db..f700802d6bc 100644 --- a/app/assets/javascripts/notes/components/note_header.vue +++ b/app/assets/javascripts/notes/components/note_header.vue @@ -8,13 +8,14 @@ import { } from '@gitlab/ui'; import { mapActions } from 'vuex'; import { __, s__ } from '~/locale'; -import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] }, components: { - timeAgoTooltip, + TimeAgoTooltip, GitlabTeamMemberBadge: () => import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'), GlIcon, @@ -26,6 +27,7 @@ export default { SafeHtml, GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], props: { author: { type: Object, @@ -183,22 +185,35 @@ export default { :data-user-id="author.id" :data-username="author.username" > - <slot name="note-header-info"></slot> + <span + v-if="glFeatures.removeUserAttributesProjects || glFeatures.removeUserAttributesGroups" + class="note-header-author-name gl-font-weight-bold" + > + {{ authorName }} + </span> <user-name-with-status + v-else :name="authorName" :availability="userAvailability(author)" container-classes="note-header-author-name gl-font-weight-bold" /> </a> <span - v-if="authorStatus" + v-if=" + authorStatus && + !glFeatures.removeUserAttributesProjects && + !glFeatures.removeUserAttributesGroups + " ref="authorStatus" v-safe-html:[$options.safeHtmlConfig]="authorStatus" v-on=" authorStatusHasTooltip ? { mouseenter: removeEmojiTitle, mouseleave: addEmojiTitle } : {} " ></span> - <span class="text-nowrap author-username"> + <span + v-if="!glFeatures.removeUserAttributesProjects && !glFeatures.removeUserAttributesGroups" + class="text-nowrap author-username" + > <a ref="authorUsernameLink" class="author-username-link" @@ -207,6 +222,7 @@ export default { @mouseleave="handleUsernameMouseLeave" ><span class="note-headline-light">@{{ author.username }}</span> </a> + <slot name="note-header-info"></slot> <gitlab-team-member-badge v-if="author && author.is_gitlab_employee" /> </span> </template> diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index c5d174ed890..afa5e39d8b0 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -10,25 +10,25 @@ import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending'; import { s__, __, sprintf } from '~/locale'; import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; -import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import eventHub from '../event_hub'; import noteable from '../mixins/noteable'; import resolvable from '../mixins/resolvable'; -import diffDiscussionHeader from './diff_discussion_header.vue'; -import diffWithNote from './diff_with_note.vue'; +import DiffDiscussionHeader from './diff_discussion_header.vue'; +import DiffWithNote from './diff_with_note.vue'; import DiscussionActions from './discussion_actions.vue'; import DiscussionNotes from './discussion_notes.vue'; -import noteForm from './note_form.vue'; -import noteSignedOutWidget from './note_signed_out_widget.vue'; +import NoteForm from './note_form.vue'; +import NoteSignedOutWidget from './note_signed_out_widget.vue'; export default { name: 'NoteableDiscussion', components: { GlIcon, - userAvatarLink, - diffDiscussionHeader, - noteSignedOutWidget, - noteForm, + UserAvatarLink, + DiffDiscussionHeader, + NoteSignedOutWidget, + NoteForm, DraftNote, TimelineEntryItem, DiscussionNotes, @@ -96,7 +96,7 @@ export default { return isLoggedIn(); }, commentType() { - return this.discussion.confidential ? __('internal note') : __('comment'); + return this.discussion.internal ? __('internal note') : __('comment'); }, autosaveKey() { return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id); @@ -108,7 +108,7 @@ export default { return this.discussion.notes.slice(0, 1)[0]; }, saveButtonTitle() { - return this.discussion.confidential ? __('Reply internally') : __('Reply'); + return this.discussion.internal ? __('Reply internally') : __('Reply'); }, shouldShowJumpToNextDiscussion() { return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion'); @@ -120,7 +120,7 @@ export default { return !this.shouldRenderDiffs; }, wrapperComponent() { - return this.shouldRenderDiffs ? diffWithNote : 'div'; + return this.shouldRenderDiffs ? DiffWithNote : 'div'; }, wrapperComponentProps() { if (this.shouldRenderDiffs) { @@ -269,6 +269,7 @@ export default { <div class="timeline-content"> <div :data-discussion-id="discussion.id" + :data-discussion-resolved="discussion.resolved" class="discussion js-discussion-container" data-qa-selector="discussion_content" > diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 875cfff74fe..e51969f95c7 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -22,16 +22,16 @@ import { commentLineOptions, formatLineRange, } from './multiline_comment_utils'; -import noteActions from './note_actions.vue'; +import NoteActions from './note_actions.vue'; import NoteBody from './note_body.vue'; -import noteHeader from './note_header.vue'; +import NoteHeader from './note_header.vue'; export default { name: 'NoteableNote', components: { GlSprintf, - noteHeader, - noteActions, + NoteHeader, + NoteActions, NoteBody, TimelineEntryItem, GlAvatarLink, @@ -109,7 +109,7 @@ export default { return this.note.author; }, commentType() { - return this.note.confidential ? __('internal note') : __('comment'); + return this.note.internal ? __('internal note') : __('comment'); }, classNameBindings() { return { @@ -259,7 +259,7 @@ export default { }); const confirmed = await confirmAction(msg, { primaryBtnVariant: 'danger', - primaryBtnText: this.note.confidential ? __('Delete internal note') : __('Delete comment'), + primaryBtnText: this.note.internal ? __('Delete internal note') : __('Delete comment'), }); if (confirmed) { @@ -406,7 +406,7 @@ export default { <template> <timeline-entry-item :id="noteAnchorId" - :class="{ ...classNameBindings, 'internal-note': note.confidential }" + :class="{ ...classNameBindings, 'internal-note': note.internal }" :data-award-url="note.toggle_award_path" :data-note-id="note.id" class="note note-wrapper" @@ -440,7 +440,7 @@ export default { </gl-avatar-link> </div> - <div v-else class="gl-float-left gl-pl-3 gl-mr-3 gl-md-pl-2 gl-md-pr-2"> + <div v-else class="gl-float-left gl-pl-3 gl-md-pl-2"> <gl-avatar-link :href="author.path"> <gl-avatar :src="author.avatar_url" @@ -459,7 +459,7 @@ export default { :author="author" :created-at="note.created_at" :note-id="note.id" - :is-internal-note="note.confidential" + :is-internal-note="note.internal" :noteable-type="noteableType" > <template #note-header-info> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 754c2917182..37bc8bad305 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -6,34 +6,34 @@ import { __ } from '~/locale'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import OrderedLayout from '~/vue_shared/components/ordered_layout.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import draftNote from '~/batch_comments/components/draft_note.vue'; +import DraftNote from '~/batch_comments/components/draft_note.vue'; import { getLocationHash, doesHashExistInUrl } from '~/lib/utils/url_utility'; -import placeholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; -import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; -import skeletonLoadingContainer from '~/vue_shared/components/notes/skeleton_note.vue'; -import systemNote from '~/vue_shared/components/notes/system_note.vue'; +import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; +import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; +import SkeletonLoadingContainer from '~/vue_shared/components/notes/skeleton_note.vue'; +import SystemNote from '~/vue_shared/components/notes/system_note.vue'; import * as constants from '../constants'; import eventHub from '../event_hub'; -import commentForm from './comment_form.vue'; -import discussionFilterNote from './discussion_filter_note.vue'; -import noteableDiscussion from './noteable_discussion.vue'; -import noteableNote from './noteable_note.vue'; +import CommentForm from './comment_form.vue'; +import DiscussionFilterNote from './discussion_filter_note.vue'; +import NoteableDiscussion from './noteable_discussion.vue'; +import NoteableNote from './noteable_note.vue'; import SidebarSubscription from './sidebar_subscription.vue'; export default { name: 'NotesApp', components: { - noteableNote, - noteableDiscussion, - systemNote, - commentForm, - placeholderNote, - placeholderSystemNote, - skeletonLoadingContainer, - discussionFilterNote, + NoteableNote, + NoteableDiscussion, + SystemNote, + CommentForm, + PlaceholderNote, + PlaceholderSystemNote, + SkeletonLoadingContainer, + DiscussionFilterNote, OrderedLayout, SidebarSubscription, - draftNote, + DraftNote, TimelineEntryItem, }, mixins: [glFeatureFlagsMixin()], diff --git a/app/assets/javascripts/notes/components/sidebar_subscription.vue b/app/assets/javascripts/notes/components/sidebar_subscription.vue index 52dadc7b4c3..9fc11ff65d5 100644 --- a/app/assets/javascripts/notes/components/sidebar_subscription.vue +++ b/app/assets/javascripts/notes/components/sidebar_subscription.vue @@ -3,7 +3,7 @@ import { mapActions } from 'vuex'; import { IssuableType } from '~/issues/constants'; import { fetchPolicies } from '~/lib/graphql'; import { confidentialityQueries } from '~/sidebar/constants'; -import { defaultClient as gqlClient } from '~/sidebar/graphql'; +import { defaultClient as gqlClient } from '~/graphql_shared/issuable_client'; export default { props: { diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue deleted file mode 100644 index bcc5d12b7c8..00000000000 --- a/app/assets/javascripts/notes/components/sort_discussion.vue +++ /dev/null @@ -1,76 +0,0 @@ -<script> -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { mapActions, mapGetters } from 'vuex'; -import { __ } from '~/locale'; -import Tracking from '~/tracking'; -import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; -import { ASC, DESC } from '../constants'; - -const SORT_OPTIONS = [ - { key: DESC, text: __('Newest first'), cls: 'js-newest-first' }, - { key: ASC, text: __('Oldest first'), cls: 'js-oldest-first' }, -]; - -export default { - SORT_OPTIONS, - components: { - GlDropdown, - GlDropdownItem, - LocalStorageSync, - }, - mixins: [Tracking.mixin()], - computed: { - ...mapGetters(['sortDirection', 'persistSortOrder', 'noteableType']), - selectedOption() { - return SORT_OPTIONS.find(({ key }) => this.sortDirection === key); - }, - dropdownText() { - return this.selectedOption.text; - }, - storageKey() { - return `sort_direction_${this.noteableType.toLowerCase()}`; - }, - }, - methods: { - ...mapActions(['setDiscussionSortDirection']), - fetchSortedDiscussions(direction) { - if (this.isDropdownItemActive(direction)) { - return; - } - - this.setDiscussionSortDirection({ direction }); - this.track('change_discussion_sort_direction', { property: direction }); - }, - isDropdownItemActive(sortDir) { - return sortDir === this.sortDirection; - }, - }, -}; -</script> - -<template> - <div - data-testid="sort-discussion-filter" - class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile" - > - <local-storage-sync - :value="sortDirection" - :storage-key="storageKey" - :persist="persistSortOrder" - as-string - @input="setDiscussionSortDirection({ direction: $event })" - /> - <gl-dropdown :text="dropdownText" class="js-dropdown-text full-width-mobile"> - <gl-dropdown-item - v-for="{ text, key, cls } in $options.SORT_OPTIONS" - :key="key" - :class="cls" - :is-check-item="true" - :is-checked="isDropdownItemActive(key)" - @click="fetchSortedDiscussions(key)" - > - {{ text }} - </gl-dropdown-item> - </gl-dropdown> - </div> -</template> diff --git a/app/assets/javascripts/notes/components/timeline_toggle.vue b/app/assets/javascripts/notes/components/timeline_toggle.vue index e4d89f54652..8632eea5d8e 100644 --- a/app/assets/javascripts/notes/components/timeline_toggle.vue +++ b/app/assets/javascripts/notes/components/timeline_toggle.vue @@ -53,7 +53,6 @@ export default { :selected="timelineEnabled" :title="tooltip" :aria-label="tooltip" - class="gl-mr-3" @click="toggleTimeline" /> </template> |