mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[GH-25484] Fix draft removal on post deletion (#25715)
* [GH-25484] Fix draft removal on post deletion * [GH-25484] Add batch migration to remove orphan drafts * [GH-25484] Fix tests of migration and draft store * [GH-25484] Remove translation file changes. * [GH-25484] Remove translation file changes. --------- Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
This commit is contained in:
@@ -37,13 +37,10 @@ export function setGlobalItem(name: string, value: any) {
|
||||
};
|
||||
}
|
||||
|
||||
export function removeGlobalItem(name: string): NewActionFunc {
|
||||
return (dispatch) => {
|
||||
dispatch({
|
||||
type: StorageTypes.REMOVE_GLOBAL_ITEM,
|
||||
data: {name},
|
||||
});
|
||||
return {data: true};
|
||||
export function removeGlobalItem(name: string) {
|
||||
return {
|
||||
type: StorageTypes.REMOVE_GLOBAL_ITEM,
|
||||
data: {name},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Client4} from 'mattermost-redux/client';
|
||||
import {Posts, Preferences} from 'mattermost-redux/constants';
|
||||
import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils';
|
||||
|
||||
import {setGlobalItem} from 'actions/storage';
|
||||
import {removeGlobalItem, setGlobalItem} from 'actions/storage';
|
||||
|
||||
import mockStore from 'tests/test_store';
|
||||
import {StoragePrefixes} from 'utils/constants';
|
||||
@@ -174,6 +174,8 @@ describe('draft actions', () => {
|
||||
uploadsInProgress: [],
|
||||
}));
|
||||
|
||||
testStore.dispatch(removeGlobalItem(StoragePrefixes.DRAFT + channelId));
|
||||
|
||||
expect(store.getActions()).toEqual(testStore.getActions());
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {PostMetadata, PostPriorityMetadata} from '@mattermost/types/posts';
|
||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getPost} from 'mattermost-redux/actions/posts';
|
||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
@@ -16,7 +17,7 @@ import {syncedDraftsAreAllowedAndEnabled} from 'mattermost-redux/selectors/entit
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import type {NewActionFunc, NewActionFuncAsync} from 'mattermost-redux/types/actions';
|
||||
|
||||
import {setGlobalItem} from 'actions/storage';
|
||||
import {removeGlobalItem, setGlobalItem} from 'actions/storage';
|
||||
import {makeGetDrafts} from 'selectors/drafts';
|
||||
import {getConnectionId} from 'selectors/general';
|
||||
import {getGlobalItem} from 'selectors/storage';
|
||||
@@ -44,13 +45,40 @@ export function getDrafts(teamId: string): NewActionFuncAsync<boolean, GlobalSta
|
||||
|
||||
let serverDrafts: Draft[] = [];
|
||||
try {
|
||||
serverDrafts = (await Client4.getUserDrafts(teamId)).map((draft) => transformServerDraft(draft));
|
||||
const response = await Client4.getUserDrafts(teamId);
|
||||
|
||||
// check if response is an array
|
||||
if (Array.isArray(response)) {
|
||||
serverDrafts = response.map((draft) => transformServerDraft(draft));
|
||||
}
|
||||
} catch (error) {
|
||||
return {data: false, error};
|
||||
}
|
||||
|
||||
const drafts = [...serverDrafts];
|
||||
const localDrafts = getLocalDrafts(state);
|
||||
const drafts = [...serverDrafts, ...localDrafts];
|
||||
|
||||
// drafts that are not on server, but on local storage
|
||||
const localOnlyDrafts = localDrafts.filter((localDraft) => {
|
||||
return !serverDrafts.find((serverDraft) => serverDraft.key === localDraft.key);
|
||||
});
|
||||
|
||||
// check if drafts are still valid
|
||||
await Promise.all(localOnlyDrafts.map(async (draft) => {
|
||||
if (draft.value.rootId) {
|
||||
// get post from server to check if it exists
|
||||
const {error} = await dispatch(getPost(draft.value.rootId));
|
||||
|
||||
// remove locally stored draft if post does not exist
|
||||
if (error.status_code === 404) {
|
||||
await dispatch(setGlobalItem(draft.key, {message: '', fileInfos: [], uploadsInProgress: []}));
|
||||
await dispatch(removeGlobalItem(draft.key));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
drafts.push(draft);
|
||||
}));
|
||||
|
||||
// Reconcile drafts and only keep the latest version of a draft.
|
||||
const draftsMap = new Map(drafts.map((draft) => [draft.key, draft]));
|
||||
@@ -74,7 +102,11 @@ export function removeDraft(key: string, channelId: string, rootId = ''): NewAct
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
dispatch(setGlobalItem(key, {message: '', fileInfos: [], uploadsInProgress: []}));
|
||||
// set draft to empty to re-render the component
|
||||
await dispatch(setGlobalItem(key, {message: '', fileInfos: [], uploadsInProgress: []}));
|
||||
|
||||
// remove draft from storage
|
||||
await dispatch(removeGlobalItem(key));
|
||||
|
||||
if (syncedDraftsAreAllowedAndEnabled(state)) {
|
||||
const connectionId = getConnectionId(getState());
|
||||
|
||||
@@ -100,7 +100,7 @@ import {redirectUserToDefaultTeam} from 'actions/global_actions';
|
||||
import {sendDesktopNotification} from 'actions/notification_actions.jsx';
|
||||
import {handleNewPost} from 'actions/post_actions';
|
||||
import * as StatusActions from 'actions/status_actions';
|
||||
import {setGlobalItem} from 'actions/storage';
|
||||
import {removeGlobalItem, setGlobalItem} from 'actions/storage';
|
||||
import {loadProfilesForDM, loadProfilesForGM} from 'actions/user_actions';
|
||||
import {syncPostsInChannel} from 'actions/views/channel';
|
||||
import {setGlobalDraft, transformServerDraft} from 'actions/views/drafts';
|
||||
@@ -118,7 +118,7 @@ import RemovedFromChannelModal from 'components/removed_from_channel_modal';
|
||||
import WebSocketClient from 'client/web_websocket_client';
|
||||
import {loadPlugin, loadPluginsIfNecessary, removePlugin} from 'plugins';
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, WarnMetricTypes, PageLoadContext} from 'utils/constants';
|
||||
import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, WarnMetricTypes, PageLoadContext, StoragePrefixes} from 'utils/constants';
|
||||
import {getSiteURL} from 'utils/url';
|
||||
|
||||
import {temporarilySetPageLoadContext} from './telemetry_actions';
|
||||
@@ -786,6 +786,19 @@ async function handlePostDeleteEvent(msg) {
|
||||
|
||||
dispatch(postDeleted(post));
|
||||
|
||||
// remove draft associated with this post from store
|
||||
const draftKey = `${StoragePrefixes.COMMENT_DRAFT}${post.id}`;
|
||||
|
||||
// update the draft first to re-render
|
||||
await dispatch(setGlobalItem(draftKey, {
|
||||
message: '',
|
||||
fileInfos: [],
|
||||
uploadsInProgress: [],
|
||||
}));
|
||||
|
||||
// then remove it
|
||||
await dispatch(removeGlobalItem(draftKey));
|
||||
|
||||
// update thread when a comment is deleted and CRT is on
|
||||
if (post.root_id && collapsedThreads) {
|
||||
const thread = getThread(state, post.root_id);
|
||||
@@ -1757,11 +1770,15 @@ function handleDeleteDraftEvent(msg) {
|
||||
const draft = JSON.parse(msg.data.draft);
|
||||
const {key} = transformServerDraft(draft);
|
||||
|
||||
doDispatch(setGlobalItem(key, {
|
||||
// update the draft first to re-render
|
||||
await doDispatch(setGlobalItem(key, {
|
||||
message: '',
|
||||
fileInfos: [],
|
||||
uploadsInProgress: [],
|
||||
}));
|
||||
|
||||
// then remove it
|
||||
await doDispatch(removeGlobalItem(key));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user