diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-29 09:09:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-29 09:09:31 +0300 |
commit | f2ba923aa70596b5ca56cbf8b58ac33dc208c6a8 (patch) | |
tree | dc8838b0323bdc7a0763b7caa18776052aa65a3a /app/assets/javascripts/notes | |
parent | 5e5c529ef67c6902c69613dd0d490613fa9ef505 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/notes')
10 files changed, 142 insertions, 3 deletions
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/constants.js b/app/assets/javascripts/notes/constants.js index a5f459c8910..88f438975f6 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -13,6 +13,7 @@ export const MERGED = 'merged'; export const ISSUE_NOTEABLE_TYPE = 'Issue'; export const EPIC_NOTEABLE_TYPE = 'Epic'; export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest'; +export const INCIDENT_NOTEABLE_TYPE = 'INCIDENT'; // TODO: check if value can be converted to `Incident` export const UNRESOLVE_NOTE_METHOD_NAME = 'delete'; export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const DESCRIPTION_TYPE = 'changed the description'; @@ -31,6 +32,7 @@ export const NOTEABLE_TYPE_MAPPING = { Issue: ISSUE_NOTEABLE_TYPE, MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE, Epic: EPIC_NOTEABLE_TYPE, + Incident: INCIDENT_NOTEABLE_TYPE, }; export const DISCUSSION_FILTER_TYPES = { diff --git a/app/assets/javascripts/notes/graphql/promote_timeline_event.mutation.graphql b/app/assets/javascripts/notes/graphql/promote_timeline_event.mutation.graphql new file mode 100644 index 00000000000..c9df9cfd6d3 --- /dev/null +++ b/app/assets/javascripts/notes/graphql/promote_timeline_event.mutation.graphql @@ -0,0 +1,8 @@ +mutation PromoteTimelineEvent($input: TimelineEventPromoteFromNoteInput!) { + timelineEventPromoteFromNote(input: $input) { + timelineEvent { + id + } + errors + } +} diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 27e54a1ea69..054a5bd36e2 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; import NotesApp from './components/notes_app.vue'; import initDiscussionFilters from './discussion_filters'; import { store } from './stores'; @@ -39,6 +40,7 @@ export default () => { username: parsedUserData.username, avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url, path: parsedUserData.path, + can_add_timeline_events: parseBoolean(notesDataset.canAddTimelineEvents), }; } diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 82417c9134b..fcef26d720c 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -6,6 +6,7 @@ import createFlash from '~/flash'; import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants'; import axios from '~/lib/utils/axios_utils'; import { __, sprintf } from '~/locale'; +import toast from '~/vue_shared/plugins/global_toast'; import { confidentialWidget } from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql'; import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql'; @@ -18,6 +19,12 @@ import sidebarTimeTrackingEventHub from '~/sidebar/event_hub'; import TaskList from '~/task_list'; import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub'; import SidebarStore from '~/sidebar/stores/sidebar_store'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_NOTE } from '~/graphql_shared/constants'; +import notesEventHub from '../event_hub'; + +import promoteTimelineEvent from '../graphql/promote_timeline_event.mutation.graphql'; + import * as constants from '../constants'; import * as types from './mutation_types'; import * as utils from './utils'; @@ -226,6 +233,54 @@ export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) }); }; +export const promoteCommentToTimelineEvent = ( + { commit }, + { noteId, addError, addGenericError }, +) => { + commit(types.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS, true); // Set loading state + return utils.gqClient + .mutate({ + mutation: promoteTimelineEvent, + variables: { + input: { + noteId: convertToGraphQLId(TYPE_NOTE, noteId), + }, + }, + }) + .then(({ data = {} }) => { + const errors = data.timelineEventPromoteFromNote?.errors; + if (errors.length) { + const errorMessage = sprintf(addError, { + error: errors.join('. '), + }); + throw new Error(errorMessage); + } else { + notesEventHub.$emit('comment-promoted-to-timeline-event'); + toast(__('Comment added to the timeline.')); + } + }) + .catch((error) => { + const message = error.message || addGenericError; + + let captureError = false; + let errorObj = null; + + if (message === addGenericError) { + captureError = true; + errorObj = error; + } + + createFlash({ + message, + captureError, + error: errorObj, + }); + }) + .finally(() => { + commit(types.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS, false); // Revert loading state + }); +}; + export const replyToDiscussion = ( { commit, state, getters, dispatch }, { endpoint, data: reply }, diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 1fe82d96435..6876220f75c 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -93,6 +93,13 @@ export const getUserDataByProp = (state) => (prop) => state.userData && state.us export const descriptionVersions = (state) => state.descriptionVersions; +export const canUserAddIncidentTimelineEvents = (state) => { + return ( + state.userData.can_add_timeline_events && + state.noteableData.type === constants.NOTEABLE_TYPE_MAPPING.Incident + ); +}; + export const notesById = (state) => state.discussions.reduce((acc, note) => { note.notes.every((n) => Object.assign(acc, { [n.id]: n })); diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index f779aad5679..7ba1f470b05 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -30,6 +30,7 @@ export default () => ({ isNotesFetched: false, isLoading: true, isLoadingDescriptionVersion: false, + isPromoteCommentToTimelineEventInProgress: false, // holds endpoints and permissions provided through haml notesData: { diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index e28a7bc5cdd..42df6bc0980 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -57,3 +57,6 @@ export const RECEIVE_DESCRIPTION_VERSION_ERROR = 'RECEIVE_DESCRIPTION_VERSION_ER export const REQUEST_DELETE_DESCRIPTION_VERSION = 'REQUEST_DELETE_DESCRIPTION_VERSION'; export const RECEIVE_DELETE_DESCRIPTION_VERSION = 'RECEIVE_DELETE_DESCRIPTION_VERSION'; export const RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR = 'RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR'; + +// Incidents +export const SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS = 'SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 0823eacf1b7..83c15c12eac 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -425,4 +425,7 @@ export default { [types.SET_DONE_FETCHING_BATCH_DISCUSSIONS](state, value) { state.doneFetchingBatchDiscussions = value; }, + [types.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS](state, value) { + state.isPromoteCommentToTimelineEventInProgress = value; + }, }; |