debian-mirror-gitlab/app/assets/javascripts/notes/stores/actions.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

900 lines
27 KiB
JavaScript
Raw Normal View History

2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2018-03-17 18:26:18 +05:30
import Visibility from 'visibilityjs';
2021-03-11 19:13:27 +05:30
import Vue from 'vue';
import Api from '~/api';
2023-05-27 22:25:52 +05:30
import { createAlert, VARIANT_INFO } from '~/alert';
2021-04-17 20:07:23 +05:30
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
2023-05-27 22:25:52 +05:30
import { STATUS_CLOSED, STATUS_REOPENED, TYPE_ISSUE } from '~/issues/constants';
2020-01-01 13:55:28 +05:30
import axios from '~/lib/utils/axios_utils';
2021-03-11 19:13:27 +05:30
import { __, sprintf } from '~/locale';
2022-10-11 01:57:18 +05:30
import toast from '~/vue_shared/plugins/global_toast';
2021-04-17 20:07:23 +05:30
import { confidentialWidget } from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
2023-03-04 22:38:38 +05:30
import updateIssueLockMutation from '~/sidebar/queries/update_issue_lock.mutation.graphql';
import updateMergeRequestLockMutation from '~/sidebar/queries/update_merge_request_lock.mutation.graphql';
2022-06-21 17:19:12 +05:30
import loadAwardsHandler from '~/awards_handler';
import { isInViewport, scrollToElement, isInMRPage } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll';
import { create } from '~/lib/utils/recurrence';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import sidebarTimeTrackingEventHub from '~/sidebar/event_hub';
import TaskList from '~/task_list';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
2022-10-11 01:57:18 +05:30
import { convertToGraphQLId } from '~/graphql_shared/utils';
2023-04-23 21:23:45 +05:30
import { TYPENAME_NOTE } from '~/graphql_shared/constants';
2022-10-11 01:57:18 +05:30
import notesEventHub from '../event_hub';
import promoteTimelineEvent from '../graphql/promote_timeline_event.mutation.graphql';
2021-03-11 19:13:27 +05:30
import * as constants from '../constants';
import * as types from './mutation_types';
import * as utils from './utils';
2018-03-17 18:26:18 +05:30
2021-09-04 01:27:46 +05:30
const NOTES_POLLING_INTERVAL = 6000;
2018-03-17 18:26:18 +05:30
let eTagPoll;
2020-10-24 23:57:45 +05:30
export const updateLockedAttribute = ({ commit, getters }, { locked, fullPath }) => {
const { iid, targetType } = getters.getNoteableData;
2020-07-28 23:09:34 +05:30
return utils.gqClient
.mutate({
2023-04-23 21:23:45 +05:30
mutation:
targetType === TYPE_ISSUE ? updateIssueLockMutation : updateMergeRequestLockMutation,
2020-07-28 23:09:34 +05:30
variables: {
input: {
projectPath: fullPath,
iid: String(iid),
2020-10-24 23:57:45 +05:30
locked,
2020-07-28 23:09:34 +05:30
},
},
})
.then(({ data }) => {
2020-10-24 23:57:45 +05:30
const discussionLocked =
2023-04-23 21:23:45 +05:30
targetType === TYPE_ISSUE
2020-10-24 23:57:45 +05:30
? data.issueSetLocked.issue.discussionLocked
: data.mergeRequestSetLocked.mergeRequest.discussionLocked;
2020-07-28 23:09:34 +05:30
2020-10-24 23:57:45 +05:30
commit(types.SET_ISSUABLE_LOCK, discussionLocked);
2020-07-28 23:09:34 +05:30
});
};
2019-02-15 15:39:39 +05:30
export const expandDiscussion = ({ commit, dispatch }, data) => {
if (data.discussionId) {
dispatch('diffs/renderFileForDiscussionId', data.discussionId, { root: true });
}
commit(types.EXPAND_DISCUSSION, data);
};
2018-11-08 19:23:39 +05:30
export const collapseDiscussion = ({ commit }, data) => commit(types.COLLAPSE_DISCUSSION, data);
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
2020-07-28 23:09:34 +05:30
export const setConfidentiality = ({ commit }, data) => commit(types.SET_ISSUE_CONFIDENTIAL, data);
2018-11-08 19:23:39 +05:30
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, discussions) =>
2021-12-11 22:18:48 +05:30
commit(types.ADD_OR_UPDATE_DISCUSSIONS, discussions);
2018-11-08 19:23:39 +05:30
export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
export const setNotesFetchedState = ({ commit }, state) =>
commit(types.SET_NOTES_FETCHED_STATE, state);
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
2020-04-22 19:07:51 +05:30
export const setExpandDiscussions = ({ commit }, { discussionIds, expanded }) => {
commit(types.SET_EXPAND_DISCUSSIONS, { discussionIds, expanded });
};
2022-08-13 15:12:31 +05:30
export const fetchDiscussions = (
{ commit, dispatch, getters },
{ path, filter, persistFilter },
) => {
2020-04-08 14:13:33 +05:30
const config =
filter !== undefined
? { params: { notes_filter: filter, persist_filter: persistFilter } }
: null;
2022-07-23 23:45:48 +05:30
if (
2022-08-13 15:12:31 +05:30
getters.noteableType === constants.ISSUE_NOTEABLE_TYPE ||
2023-03-17 16:20:25 +05:30
getters.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
2022-07-23 23:45:48 +05:30
) {
2021-12-11 22:18:48 +05:30
return dispatch('fetchDiscussionsBatch', { path, config, perPage: 20 });
}
2020-04-08 14:13:33 +05:30
return axios.get(path, config).then(({ data }) => {
2021-12-11 22:18:48 +05:30
commit(types.ADD_OR_UPDATE_DISCUSSIONS, data);
2020-11-24 15:15:51 +05:30
commit(types.SET_FETCHING_DISCUSSIONS, false);
2020-04-22 19:07:51 +05:30
2019-12-04 20:38:33 +05:30
dispatch('updateResolvableDiscussionsCounts');
});
2020-04-08 14:13:33 +05:30
};
2018-11-08 19:23:39 +05:30
2023-03-04 22:38:38 +05:30
export const fetchNotes = ({ dispatch, getters }) => {
if (getters.isFetching) return null;
dispatch('setFetchingState', true);
return dispatch('fetchDiscussions', getters.getFetchDiscussionsConfig)
.then(() => dispatch('initPolling'))
.then(() => {
dispatch('setLoadingState', false);
dispatch('setNotesFetchedState', true);
notesEventHub.$emit('fetchedNotesData');
dispatch('setFetchingState', false);
})
.catch(() => {
dispatch('setLoadingState', false);
dispatch('setNotesFetchedState', true);
createAlert({
message: __('Something went wrong while fetching comments. Please try again.'),
});
});
};
export const initPolling = ({ state, dispatch, getters, commit }) => {
if (state.isPollingInitialized) {
return;
}
dispatch('setLastFetchedAt', getters.getNotesDataByProp('lastFetchedAt'));
dispatch('poll');
commit(types.SET_IS_POLLING_INITIALIZED, true);
};
2021-12-11 22:18:48 +05:30
export const fetchDiscussionsBatch = ({ commit, dispatch }, { path, config, cursor, perPage }) => {
const params = { ...config?.params, per_page: perPage };
if (cursor) {
params.cursor = cursor;
}
return axios.get(path, { params }).then(({ data, headers }) => {
commit(types.ADD_OR_UPDATE_DISCUSSIONS, data);
2022-08-13 15:12:31 +05:30
if (headers && headers['x-next-page-cursor']) {
2021-12-11 22:18:48 +05:30
const nextConfig = { ...config };
if (config?.params?.persist_filter) {
delete nextConfig.params.notes_filter;
delete nextConfig.params.persist_filter;
}
return dispatch('fetchDiscussionsBatch', {
path,
config: nextConfig,
cursor: headers['x-next-page-cursor'],
perPage: Math.min(Math.round(perPage * 1.5), 100),
});
}
2022-07-23 23:45:48 +05:30
commit(types.SET_DONE_FETCHING_BATCH_DISCUSSIONS, true);
2021-12-11 22:18:48 +05:30
commit(types.SET_FETCHING_DISCUSSIONS, false);
dispatch('updateResolvableDiscussionsCounts');
return undefined;
});
};
2018-12-05 23:21:45 +05:30
export const updateDiscussion = ({ commit, state }, discussion) => {
commit(types.UPDATE_DISCUSSION, discussion);
return utils.findNoteObjectById(state.discussions, discussion.id);
};
2018-03-17 18:26:18 +05:30
2021-01-03 14:25:43 +05:30
export const setDiscussionSortDirection = ({ commit }, { direction, persist = true }) => {
commit(types.SET_DISCUSSIONS_SORT, { direction, persist });
};
export const setTimelineView = ({ commit }, enabled) => {
commit(types.SET_TIMELINE_VIEW, enabled);
2020-04-22 19:07:51 +05:30
};
2020-07-28 23:09:34 +05:30
export const setSelectedCommentPosition = ({ commit }, position) => {
commit(types.SET_SELECTED_COMMENT_POSITION, position);
};
export const setSelectedCommentPositionHover = ({ commit }, position) => {
commit(types.SET_SELECTED_COMMENT_POSITION_HOVER, position);
};
2019-12-04 20:38:33 +05:30
export const removeNote = ({ commit, dispatch, state }, note) => {
const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
2019-02-15 15:39:39 +05:30
2019-12-04 20:38:33 +05:30
commit(types.DELETE_NOTE, note);
2018-12-05 23:21:45 +05:30
2019-12-04 20:38:33 +05:30
dispatch('updateMergeRequestWidget');
dispatch('updateResolvableDiscussionsCounts');
2019-02-15 15:39:39 +05:30
2019-12-04 20:38:33 +05:30
if (isInMRPage()) {
dispatch('diffs/removeDiscussionsFromDiff', discussion);
}
};
export const deleteNote = ({ dispatch }, note) =>
axios.delete(note.path).then(() => {
dispatch('removeNote', note);
2018-03-17 18:26:18 +05:30
});
2019-02-15 15:39:39 +05:30
export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
2020-04-08 14:13:33 +05:30
axios.put(endpoint, note).then(({ data }) => {
2019-12-04 20:38:33 +05:30
commit(types.UPDATE_NOTE, data);
dispatch('startTaskList');
});
2018-03-17 18:26:18 +05:30
2019-07-07 11:18:12 +05:30
export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => {
const { notesById } = getters;
2021-03-08 18:12:59 +05:30
const debouncedFetchDiscussions = (isFetching) => {
2020-11-24 15:15:51 +05:30
if (!isFetching) {
commit(types.SET_FETCHING_DISCUSSIONS, true);
dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
} else {
if (isFetching !== true) {
clearTimeout(state.currentlyFetchingDiscussions);
}
commit(
types.SET_FETCHING_DISCUSSIONS,
setTimeout(() => {
dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
}, constants.DISCUSSION_FETCH_TIMEOUT),
);
}
};
2019-07-07 11:18:12 +05:30
2021-03-08 18:12:59 +05:30
notes.forEach((note) => {
2019-07-07 11:18:12 +05:30
if (notesById[note.id]) {
commit(types.UPDATE_NOTE, note);
} else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id);
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
2021-01-29 00:20:46 +05:30
} else if (note.type === constants.DIFF_NOTE && !note.base_discussion) {
2020-11-24 15:15:51 +05:30
debouncedFetchDiscussions(state.currentlyFetchingDiscussions);
2019-07-07 11:18:12 +05:30
} else {
commit(types.ADD_NEW_NOTE, note);
}
} else {
commit(types.ADD_NEW_NOTE, note);
}
});
};
2022-10-11 01:57:18 +05:30
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: {
2023-04-23 21:23:45 +05:30
noteId: convertToGraphQLId(TYPENAME_NOTE, noteId),
2022-10-11 01:57:18 +05:30
},
},
})
.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;
}
2023-03-04 22:38:38 +05:30
createAlert({
2022-10-11 01:57:18 +05:30
message,
captureError,
error: errorObj,
});
})
.finally(() => {
commit(types.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS, false); // Revert loading state
});
};
2019-12-04 20:38:33 +05:30
export const replyToDiscussion = (
{ commit, state, getters, dispatch },
{ endpoint, data: reply },
) =>
2020-04-08 14:13:33 +05:30
axios.post(endpoint, reply).then(({ data }) => {
2019-12-04 20:38:33 +05:30
if (data.discussion) {
commit(types.UPDATE_DISCUSSION, data.discussion);
2019-07-07 11:18:12 +05:30
2019-12-04 20:38:33 +05:30
updateOrCreateNotes({ commit, state, getters, dispatch }, data.discussion.notes);
2019-07-07 11:18:12 +05:30
2019-12-04 20:38:33 +05:30
dispatch('updateMergeRequestWidget');
dispatch('startTaskList');
dispatch('updateResolvableDiscussionsCounts');
} else {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, data);
}
2018-03-17 18:26:18 +05:30
2019-12-04 20:38:33 +05:30
return data;
});
2018-03-17 18:26:18 +05:30
2019-12-04 20:38:33 +05:30
export const createNewNote = ({ commit, dispatch }, { endpoint, data: reply }) =>
2020-04-08 14:13:33 +05:30
axios.post(endpoint, reply).then(({ data }) => {
2019-12-04 20:38:33 +05:30
if (!data.errors) {
commit(types.ADD_NEW_NOTE, data);
dispatch('updateMergeRequestWidget');
dispatch('startTaskList');
dispatch('updateResolvableDiscussionsCounts');
}
return data;
});
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
2018-03-17 18:26:18 +05:30
2019-07-31 22:56:46 +05:30
export const resolveDiscussion = ({ state, dispatch, getters }, { discussionId }) => {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
const isResolved = getters.isDiscussionResolved(discussionId);
if (!discussion) {
return Promise.reject();
} else if (isResolved) {
return Promise.resolve();
}
return dispatch('toggleResolveNote', {
endpoint: discussion.resolve_path,
isResolved,
discussion: true,
});
};
2020-04-08 14:13:33 +05:30
export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) => {
const method = isResolved
? constants.UNRESOLVE_NOTE_METHOD_NAME
: constants.RESOLVE_NOTE_METHOD_NAME;
const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
2018-03-27 19:54:05 +05:30
2020-04-08 14:13:33 +05:30
return axios[method](endpoint).then(({ data }) => {
2019-12-04 20:38:33 +05:30
commit(mutationType, data);
2018-12-05 23:21:45 +05:30
2019-12-04 20:38:33 +05:30
dispatch('updateResolvableDiscussionsCounts');
2019-02-15 15:39:39 +05:30
2019-12-04 20:38:33 +05:30
dispatch('updateMergeRequestWidget');
});
2020-04-08 14:13:33 +05:30
};
2018-03-27 19:54:05 +05:30
2021-02-22 17:27:13 +05:30
export const closeIssuable = ({ commit, dispatch, state }) => {
2018-03-27 19:54:05 +05:30
dispatch('toggleStateButtonLoading', true);
2020-04-08 14:13:33 +05:30
return axios.put(state.notesData.closePath).then(({ data }) => {
2019-12-04 20:38:33 +05:30
commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
});
2018-03-27 19:54:05 +05:30
};
2021-02-22 17:27:13 +05:30
export const reopenIssuable = ({ commit, dispatch, state }) => {
2018-03-27 19:54:05 +05:30
dispatch('toggleStateButtonLoading', true);
2020-04-08 14:13:33 +05:30
return axios.put(state.notesData.reopenPath).then(({ data }) => {
2019-12-04 20:38:33 +05:30
commit(types.REOPEN_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
});
2018-03-27 19:54:05 +05:30
};
export const toggleStateButtonLoading = ({ commit }, value) =>
commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
2018-11-08 19:23:39 +05:30
export const emitStateChangedEvent = ({ getters }, data) => {
2021-04-17 20:07:23 +05:30
const event = new CustomEvent(EVENT_ISSUABLE_VUE_APP_CHANGE, {
2018-05-09 12:01:36 +05:30
detail: {
data,
2023-05-27 22:25:52 +05:30
isClosed: getters.openState === STATUS_CLOSED,
2018-05-09 12:01:36 +05:30
},
});
2018-03-27 19:54:05 +05:30
document.dispatchEvent(event);
};
export const toggleIssueLocalState = ({ commit }, newState) => {
2023-05-27 22:25:52 +05:30
if (newState === STATUS_CLOSED) {
2018-03-27 19:54:05 +05:30
commit(types.CLOSE_ISSUE);
2023-05-27 22:25:52 +05:30
} else if (newState === STATUS_REOPENED) {
2018-03-27 19:54:05 +05:30
commit(types.REOPEN_ISSUE);
}
};
2018-03-17 18:26:18 +05:30
export const saveNote = ({ commit, dispatch }, noteData) => {
2018-11-08 19:23:39 +05:30
// For MR discussuions we need to post as `note[note]` and issue we use `note.note`.
2018-12-05 23:21:45 +05:30
// For batch comments, we use draft_note
const note = noteData.data.draft_note || noteData.data['note[note]'] || noteData.data.note.note;
2018-03-17 18:26:18 +05:30
let placeholderText = note;
const hasQuickActions = utils.hasQuickActions(placeholderText);
const replyId = noteData.data.in_reply_to_discussion_id;
2018-12-05 23:21:45 +05:30
let methodToDispatch;
2020-05-24 23:13:21 +05:30
const postData = { ...noteData };
2018-12-05 23:21:45 +05:30
if (postData.isDraft === true) {
methodToDispatch = replyId
? 'batchComments/addDraftToDiscussion'
: 'batchComments/createNewDraft';
if (!postData.draft_note && noteData.note) {
postData.draft_note = postData.note;
delete postData.note;
}
} else {
methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote';
}
2018-03-17 18:26:18 +05:30
$('.notes-form .flash-container').hide(); // hide previous flash notification
2018-11-20 20:47:30 +05:30
commit(types.REMOVE_PLACEHOLDER_NOTES); // remove previous placeholders
2018-03-17 18:26:18 +05:30
2021-09-30 23:02:18 +05:30
if (hasQuickActions) {
placeholderText = utils.stripQuickActions(placeholderText);
}
2018-03-17 18:26:18 +05:30
2021-09-30 23:02:18 +05:30
if (placeholderText.length) {
commit(types.SHOW_PLACEHOLDER_NOTE, {
noteBody: placeholderText,
replyId,
});
}
2018-03-17 18:26:18 +05:30
2021-09-30 23:02:18 +05:30
if (hasQuickActions) {
commit(types.SHOW_PLACEHOLDER_NOTE, {
isSystemNote: true,
noteBody: utils.getQuickActionText(note),
replyId,
});
2018-03-17 18:26:18 +05:30
}
2021-03-08 18:12:59 +05:30
const processQuickActions = (res) => {
2022-07-16 23:28:13 +05:30
const {
2023-05-27 22:25:52 +05:30
errors: { commands_only: commandsOnly } = {
2022-07-16 23:28:13 +05:30
commands_only: null,
command_names: [],
},
2023-05-27 22:25:52 +05:30
command_names: commandNames,
2022-07-16 23:28:13 +05:30
} = res;
2023-05-27 22:25:52 +05:30
const message = commandsOnly;
if (commandNames?.indexOf('submit_review') >= 0) {
dispatch('batchComments/clearDrafts');
}
2022-07-16 23:28:13 +05:30
2019-12-21 20:55:43 +05:30
/*
The following reply means that quick actions have been successfully applied:
2018-03-17 18:26:18 +05:30
2019-12-21 20:55:43 +05:30
{"commands_changes":{},"valid":false,"errors":{"commands_only":["Commands applied"]}}
*/
2020-01-01 13:55:28 +05:30
if (hasQuickActions && message) {
2019-12-21 20:55:43 +05:30
eTagPoll.makeRequest();
2019-09-04 21:01:54 +05:30
2021-04-17 20:07:23 +05:30
// synchronizing the quick action with the sidebar widget
// this is a temporary solution until we have confidentiality real-time updates
if (
confidentialWidget.setConfidentiality &&
2021-06-08 01:23:25 +05:30
message.some((m) => m.includes('Made this issue confidential'))
2021-04-17 20:07:23 +05:30
) {
confidentialWidget.setConfidentiality();
}
2019-12-21 20:55:43 +05:30
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
2023-03-04 22:38:38 +05:30
createAlert({
2021-09-30 23:02:18 +05:30
message: message || __('Commands applied'),
2023-03-04 22:38:38 +05:30
variant: VARIANT_INFO,
2021-09-30 23:02:18 +05:30
parent: noteData.flashContainer,
});
2018-05-09 12:01:36 +05:30
}
2018-03-17 18:26:18 +05:30
2020-01-01 13:55:28 +05:30
return res;
2019-12-21 20:55:43 +05:30
};
2018-03-17 18:26:18 +05:30
2021-03-08 18:12:59 +05:30
const processEmojiAward = (res) => {
2019-12-21 20:55:43 +05:30
const { commands_changes: commandsChanges } = res;
const { emoji_award: emojiAward } = commandsChanges || {};
if (!emojiAward) {
return res;
2018-05-09 12:01:36 +05:30
}
2018-03-17 18:26:18 +05:30
2019-12-21 20:55:43 +05:30
const votesBlock = $('.js-awards-block').eq(0);
return loadAwardsHandler()
2021-03-08 18:12:59 +05:30
.then((awardsHandler) => {
2019-12-21 20:55:43 +05:30
awardsHandler.addAwardToEmojiBar(votesBlock, emojiAward);
awardsHandler.scrollToAwards();
})
.catch(() => {
2023-03-04 22:38:38 +05:30
createAlert({
2021-09-30 23:02:18 +05:30
message: __('Something went wrong while adding your award. Please try again.'),
parent: noteData.flashContainer,
});
2019-12-21 20:55:43 +05:30
})
.then(() => res);
};
2021-03-08 18:12:59 +05:30
const processTimeTracking = (res) => {
2019-12-21 20:55:43 +05:30
const { commands_changes: commandsChanges } = res;
const { spend_time: spendTime, time_estimate: timeEstimate } = commandsChanges || {};
if (spendTime != null || timeEstimate != null) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', {
commands_changes: commandsChanges,
});
2018-05-09 12:01:36 +05:30
}
2019-12-21 20:55:43 +05:30
return res;
};
2021-03-08 18:12:59 +05:30
const removePlaceholder = (res) => {
2021-09-30 23:02:18 +05:30
commit(types.REMOVE_PLACEHOLDER_NOTES);
2018-05-09 12:01:36 +05:30
return res;
2019-12-21 20:55:43 +05:30
};
2021-03-08 18:12:59 +05:30
const processErrors = (error) => {
2020-01-01 13:55:28 +05:30
if (error.response) {
const {
response: { data = {} },
} = error;
const { errors = {} } = data;
const { base = [] } = errors;
// we handle only errors.base for now
if (base.length > 0) {
const errorMsg = sprintf(__('Your comment could not be submitted because %{error}'), {
error: base[0].toLowerCase(),
});
2023-03-04 22:38:38 +05:30
createAlert({
2021-09-30 23:02:18 +05:30
message: errorMsg,
parent: noteData.flashContainer,
});
2023-03-04 22:38:38 +05:30
return { ...data, hasAlert: true };
2020-01-01 13:55:28 +05:30
}
}
throw error;
};
2019-12-21 20:55:43 +05:30
return dispatch(methodToDispatch, postData, { root: true })
2020-01-01 13:55:28 +05:30
.then(processQuickActions)
2019-12-21 20:55:43 +05:30
.then(processEmojiAward)
.then(processTimeTracking)
2020-01-01 13:55:28 +05:30
.then(removePlaceholder)
.catch(processErrors);
2018-03-17 18:26:18 +05:30
};
2021-03-11 19:13:27 +05:30
export const setFetchingState = ({ commit }, fetchingState) =>
commit(types.SET_NOTES_FETCHING_STATE, fetchingState);
const pollSuccessCallBack = async (resp, commit, state, getters, dispatch) => {
2021-02-22 17:27:13 +05:30
if (state.isResolvingDiscussion) {
return null;
}
2020-07-28 23:09:34 +05:30
if (resp.notes?.length) {
2021-03-11 19:13:27 +05:30
await dispatch('updateOrCreateNotes', resp.notes);
2019-02-15 15:39:39 +05:30
dispatch('startTaskList');
2021-03-11 19:13:27 +05:30
dispatch('updateResolvableDiscussionsCounts');
2018-03-17 18:26:18 +05:30
}
2018-03-27 19:54:05 +05:30
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
2018-03-17 18:26:18 +05:30
return resp;
};
2021-03-08 18:12:59 +05:30
const getFetchDataParams = (state) => {
2020-04-08 14:13:33 +05:30
const endpoint = state.notesData.notesPath;
const options = {
headers: {
'X-Last-Fetched-At': state.lastFetchedAt ? `${state.lastFetchedAt}` : undefined,
},
};
return { endpoint, options };
};
2018-11-08 19:23:39 +05:30
export const poll = ({ commit, state, getters, dispatch }) => {
2021-09-04 01:27:46 +05:30
const notePollOccurrenceTracking = create();
2023-03-04 22:38:38 +05:30
let alert;
2021-09-04 01:27:46 +05:30
notePollOccurrenceTracking.handle(1, () => {
// Since polling halts internally after 1 failure, we manually try one more time
setTimeout(() => eTagPoll.restart(), NOTES_POLLING_INTERVAL);
});
notePollOccurrenceTracking.handle(2, () => {
// On the second failure in a row, show the alert and try one more time (hoping to succeed and clear the error)
2023-03-04 22:38:38 +05:30
alert = createAlert({
2021-09-30 23:02:18 +05:30
message: __('Something went wrong while fetching latest comments.'),
});
2021-09-04 01:27:46 +05:30
setTimeout(() => eTagPoll.restart(), NOTES_POLLING_INTERVAL);
});
2018-03-17 18:26:18 +05:30
eTagPoll = new Poll({
2020-04-08 14:13:33 +05:30
resource: {
poll: () => {
const { endpoint, options } = getFetchDataParams(state);
return axios.get(endpoint, options);
},
},
2018-03-17 18:26:18 +05:30
method: 'poll',
2021-09-04 01:27:46 +05:30
successCallback: ({ data }) => {
pollSuccessCallBack(data, commit, state, getters, dispatch);
if (notePollOccurrenceTracking.count) {
notePollOccurrenceTracking.reset();
}
2023-03-04 22:38:38 +05:30
alert?.dismiss();
2021-09-04 01:27:46 +05:30
},
errorCallback: () => notePollOccurrenceTracking.occur(),
2018-03-17 18:26:18 +05:30
});
if (!Visibility.hidden()) {
2020-11-24 15:15:51 +05:30
eTagPoll.makeDelayedRequest(2500);
2018-03-17 18:26:18 +05:30
} else {
2021-06-08 01:23:25 +05:30
eTagPoll.makeRequest();
2018-03-17 18:26:18 +05:30
}
Visibility.change(() => {
if (!Visibility.hidden()) {
eTagPoll.restart();
} else {
eTagPoll.stop();
}
});
};
export const stopPolling = () => {
2019-09-30 21:07:59 +05:30
if (eTagPoll) eTagPoll.stop();
2018-03-17 18:26:18 +05:30
};
export const restartPolling = () => {
2019-09-30 21:07:59 +05:30
if (eTagPoll) eTagPoll.restart();
2018-03-17 18:26:18 +05:30
};
2018-11-08 19:23:39 +05:30
export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
2018-03-17 18:26:18 +05:30
commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
};
2018-11-08 19:23:39 +05:30
export const toggleAwardRequest = ({ dispatch }, data) => {
2018-03-17 18:26:18 +05:30
const { endpoint, awardName } = data;
2019-09-30 21:07:59 +05:30
return axios.post(endpoint, { name: awardName }).then(() => {
dispatch('toggleAward', data);
});
2018-03-17 18:26:18 +05:30
};
export const scrollToNoteIfNeeded = (context, el) => {
if (!isInViewport(el[0])) {
scrollToElement(el);
}
};
2018-10-15 14:42:47 +05:30
2018-11-08 19:23:39 +05:30
export const fetchDiscussionDiffLines = ({ commit }, discussion) =>
2019-02-15 15:39:39 +05:30
axios.get(discussion.truncated_diff_lines_path).then(({ data }) => {
2018-11-08 19:23:39 +05:30
commit(types.SET_DISCUSSION_DIFF_LINES, {
discussionId: discussion.id,
diffLines: data.truncated_diff_lines,
});
});
2018-12-05 23:21:45 +05:30
export const updateMergeRequestWidget = () => {
mrWidgetEventHub.$emit('mr.discussion.updated');
};
2018-12-13 13:39:08 +05:30
export const setLoadingState = ({ commit }, data) => {
commit(types.SET_NOTES_LOADING_STATE, data);
};
2022-01-26 12:08:38 +05:30
export const filterDiscussion = ({ commit, dispatch }, { path, filter, persistFilter }) => {
commit(types.CLEAR_DISCUSSIONS);
2018-12-13 13:39:08 +05:30
dispatch('setLoadingState', true);
2019-10-12 21:52:04 +05:30
dispatch('fetchDiscussions', { path, filter, persistFilter })
2018-12-13 13:39:08 +05:30
.then(() => {
dispatch('setLoadingState', false);
dispatch('setNotesFetchedState', true);
})
.catch(() => {
dispatch('setLoadingState', false);
dispatch('setNotesFetchedState', true);
2023-03-04 22:38:38 +05:30
createAlert({
2021-09-30 23:02:18 +05:30
message: __('Something went wrong while fetching comments. Please try again.'),
});
2018-12-13 13:39:08 +05:30
});
};
export const setCommentsDisabled = ({ commit }, data) => {
commit(types.DISABLE_COMMENTS, data);
};
2019-02-15 15:39:39 +05:30
export const startTaskList = ({ dispatch }) =>
Vue.nextTick(
() =>
new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes .is-editable',
onSuccess: () => dispatch('startTaskList'),
}),
);
2019-09-30 21:07:59 +05:30
export const updateResolvableDiscussionsCounts = ({ commit }) =>
2019-02-15 15:39:39 +05:30
commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS);
export const submitSuggestion = (
2019-07-31 22:56:46 +05:30
{ commit, dispatch },
2021-03-08 18:12:59 +05:30
{ discussionId, suggestionId, flashContainer, message },
2019-12-21 20:55:43 +05:30
) => {
const dispatchResolveDiscussion = () =>
dispatch('resolveDiscussion', { discussionId }).catch(() => {});
2021-02-22 17:27:13 +05:30
commit(types.SET_RESOLVING_DISCUSSION, true);
dispatch('stopPolling');
2021-03-08 18:12:59 +05:30
return Api.applySuggestion(suggestionId, message)
2019-12-21 20:55:43 +05:30
.then(dispatchResolveDiscussion)
2021-03-08 18:12:59 +05:30
.catch((err) => {
2019-02-15 15:39:39 +05:30
const defaultMessage = __(
'Something went wrong while applying the suggestion. Please try again.',
);
2020-06-23 00:09:42 +05:30
const errorMessage = err.response.data?.message;
2023-05-27 22:25:52 +05:30
const alertMessage = errorMessage || defaultMessage;
2019-02-15 15:39:39 +05:30
2023-03-04 22:38:38 +05:30
createAlert({
2023-05-27 22:25:52 +05:30
message: alertMessage,
2021-09-30 23:02:18 +05:30
parent: flashContainer,
});
2021-02-22 17:27:13 +05:30
})
.finally(() => {
commit(types.SET_RESOLVING_DISCUSSION, false);
dispatch('restartPolling');
2019-02-15 15:39:39 +05:30
});
2019-12-21 20:55:43 +05:30
};
2019-02-15 15:39:39 +05:30
2021-11-18 22:05:49 +05:30
export const submitSuggestionBatch = ({ commit, dispatch, state }, { message, flashContainer }) => {
2020-06-23 00:09:42 +05:30
const suggestionIds = state.batchSuggestionsInfo.map(({ suggestionId }) => suggestionId);
const resolveAllDiscussions = () =>
2021-03-08 18:12:59 +05:30
state.batchSuggestionsInfo.map((suggestionInfo) => {
2020-06-23 00:09:42 +05:30
const { discussionId } = suggestionInfo;
return dispatch('resolveDiscussion', { discussionId }).catch(() => {});
});
commit(types.SET_APPLYING_BATCH_STATE, true);
2021-02-22 17:27:13 +05:30
commit(types.SET_RESOLVING_DISCUSSION, true);
dispatch('stopPolling');
2020-06-23 00:09:42 +05:30
2021-11-18 22:05:49 +05:30
return Api.applySuggestionBatch(suggestionIds, message)
2020-06-23 00:09:42 +05:30
.then(() => Promise.all(resolveAllDiscussions()))
.then(() => commit(types.CLEAR_SUGGESTION_BATCH))
2021-03-08 18:12:59 +05:30
.catch((err) => {
2020-06-23 00:09:42 +05:30
const defaultMessage = __(
'Something went wrong while applying the batch of suggestions. Please try again.',
);
const errorMessage = err.response.data?.message;
2023-05-27 22:25:52 +05:30
const alertMessage = errorMessage || defaultMessage;
2020-06-23 00:09:42 +05:30
2023-03-04 22:38:38 +05:30
createAlert({
2023-05-27 22:25:52 +05:30
message: alertMessage,
2021-09-30 23:02:18 +05:30
parent: flashContainer,
});
2020-06-23 00:09:42 +05:30
})
2021-02-22 17:27:13 +05:30
.finally(() => {
commit(types.SET_APPLYING_BATCH_STATE, false);
commit(types.SET_RESOLVING_DISCUSSION, false);
dispatch('restartPolling');
});
2020-06-23 00:09:42 +05:30
};
export const addSuggestionInfoToBatch = ({ commit }, { suggestionId, noteId, discussionId }) =>
commit(types.ADD_SUGGESTION_TO_BATCH, { suggestionId, noteId, discussionId });
export const removeSuggestionInfoFromBatch = ({ commit }, suggestionId) =>
commit(types.REMOVE_SUGGESTION_FROM_BATCH, suggestionId);
2019-03-02 22:35:43 +05:30
export const convertToDiscussion = ({ commit }, noteId) =>
commit(types.CONVERT_TO_DISCUSSION, noteId);
2019-07-07 11:18:12 +05:30
export const removeConvertedDiscussion = ({ commit }, noteId) =>
commit(types.REMOVE_CONVERTED_DISCUSSION, noteId);
2020-03-13 15:44:24 +05:30
export const setCurrentDiscussionId = ({ commit }, discussionId) =>
commit(types.SET_CURRENT_DISCUSSION_ID, discussionId);
2020-04-08 14:13:33 +05:30
export const fetchDescriptionVersion = ({ dispatch }, { endpoint, startingVersion, versionId }) => {
2019-12-26 22:10:19 +05:30
let requestUrl = endpoint;
if (startingVersion) {
requestUrl = mergeUrlParams({ start_version_id: startingVersion }, requestUrl);
}
2020-03-13 15:44:24 +05:30
dispatch('requestDescriptionVersion');
2019-12-26 22:10:19 +05:30
return axios
.get(requestUrl)
2021-03-08 18:12:59 +05:30
.then((res) => {
2020-04-08 14:13:33 +05:30
dispatch('receiveDescriptionVersion', { descriptionVersion: res.data, versionId });
2020-03-13 15:44:24 +05:30
})
2021-03-08 18:12:59 +05:30
.catch((error) => {
2020-03-13 15:44:24 +05:30
dispatch('receiveDescriptionVersionError', error);
2023-03-04 22:38:38 +05:30
createAlert({
2021-09-30 23:02:18 +05:30
message: __('Something went wrong while fetching description changes. Please try again.'),
});
2019-12-26 22:10:19 +05:30
});
};
2020-03-13 15:44:24 +05:30
export const requestDescriptionVersion = ({ commit }) => {
commit(types.REQUEST_DESCRIPTION_VERSION);
};
export const receiveDescriptionVersion = ({ commit }, descriptionVersion) => {
commit(types.RECEIVE_DESCRIPTION_VERSION, descriptionVersion);
};
export const receiveDescriptionVersionError = ({ commit }, error) => {
commit(types.RECEIVE_DESCRIPTION_VERSION_ERROR, error);
};
2020-04-08 14:13:33 +05:30
export const softDeleteDescriptionVersion = (
{ dispatch },
{ endpoint, startingVersion, versionId },
) => {
2020-03-13 15:44:24 +05:30
let requestUrl = endpoint;
if (startingVersion) {
requestUrl = mergeUrlParams({ start_version_id: startingVersion }, requestUrl);
}
dispatch('requestDeleteDescriptionVersion');
return axios
.delete(requestUrl)
.then(() => {
2020-04-08 14:13:33 +05:30
dispatch('receiveDeleteDescriptionVersion', versionId);
2020-03-13 15:44:24 +05:30
})
2021-03-08 18:12:59 +05:30
.catch((error) => {
2020-03-13 15:44:24 +05:30
dispatch('receiveDeleteDescriptionVersionError', error);
2023-03-04 22:38:38 +05:30
createAlert({
2021-09-30 23:02:18 +05:30
message: __('Something went wrong while deleting description changes. Please try again.'),
});
2020-06-23 00:09:42 +05:30
// Throw an error here because a component like SystemNote -
// needs to know if the request failed to reset its internal state.
throw new Error();
2020-03-13 15:44:24 +05:30
});
};
export const requestDeleteDescriptionVersion = ({ commit }) => {
commit(types.REQUEST_DELETE_DESCRIPTION_VERSION);
};
2020-04-08 14:13:33 +05:30
export const receiveDeleteDescriptionVersion = ({ commit }, versionId) => {
commit(types.RECEIVE_DELETE_DESCRIPTION_VERSION, { [versionId]: __('Deleted') });
2020-03-13 15:44:24 +05:30
};
export const receiveDeleteDescriptionVersionError = ({ commit }, error) => {
commit(types.RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR, error);
};
2020-06-23 00:09:42 +05:30
export const updateAssignees = ({ commit }, assignees) => {
commit(types.UPDATE_ASSIGNEES, assignees);
};
2020-10-24 23:57:45 +05:30
export const updateDiscussionPosition = ({ commit }, updatedPosition) => {
commit(types.UPDATE_DISCUSSION_POSITION, updatedPosition);
};