[MM-34233] Only refresh App bindings if apps plugin is enabled (#5673)
Co-authored-by: Daniel Espino García <larkox@gmail.com>
This commit is contained in:
parent
e086b19d1e
commit
0a4ec73ee3
14 changed files with 119 additions and 36 deletions
|
|
@ -232,7 +232,6 @@ export function handleSelectChannel(channelId) {
|
|||
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
|
||||
|
||||
if (appsEnabled(state)) {
|
||||
//TODO improve sync method
|
||||
dispatch(fetchAppBindings(currentUserId, channelId));
|
||||
}
|
||||
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
|
|
|
|||
|
|
@ -166,6 +166,9 @@ describe('Actions.Views.Channel', () => {
|
|||
[currentTeamId]: {},
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
pluginEnabled: true,
|
||||
},
|
||||
general: {
|
||||
config: {
|
||||
EnableLegacySidebar: 'true',
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fetchAppBindings, fetchThreadAppBindings} from '@mm-redux/actions/apps';
|
||||
import {getThreadAppsBindingsChannelId} from '@mm-redux/selectors/entities/apps';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/common';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import AppsTypes from '@mm-redux/action_types/apps';
|
||||
import {refreshAppBindings} from '@mm-redux/actions/apps';
|
||||
import {ActionResult, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {appsEnabled} from '@utils/apps';
|
||||
|
||||
export function handleRefreshAppsBindings() {
|
||||
return (dispatch: DispatchFunc, getState: GetStateFunc): ActionResult => {
|
||||
const state = getState();
|
||||
if (!appsEnabled(state)) {
|
||||
return {data: true};
|
||||
}
|
||||
|
||||
dispatch(fetchAppBindings(getCurrentUserId(state), getCurrentChannelId(state)));
|
||||
|
||||
const threadChannelID = getThreadAppsBindingsChannelId(state);
|
||||
if (threadChannelID) {
|
||||
dispatch(fetchThreadAppBindings(getCurrentUserId(state), threadChannelID));
|
||||
if (appsEnabled(getState())) {
|
||||
dispatch(refreshAppBindings());
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function handleAppsPluginEnabled() {
|
||||
return {
|
||||
type: AppsTypes.APPS_PLUGIN_ENABLED,
|
||||
};
|
||||
}
|
||||
|
||||
export function handleAppsPluginDisabled() {
|
||||
return {
|
||||
type: AppsTypes.APPS_PLUGIN_DISABLED,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {loadMe} from '@actions/views/user';
|
|||
import {Client4} from '@client/rest';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import {ChannelTypes, GeneralTypes, PreferenceTypes, TeamTypes, UserTypes, RoleTypes} from '@mm-redux/action_types';
|
||||
import {refreshAppBindings} from '@mm-redux/actions/apps';
|
||||
import {getThreads} from '@mm-redux/actions/threads';
|
||||
import {getProfilesByIds, getStatusesByIds} from '@mm-redux/actions/users';
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
|
@ -37,11 +38,12 @@ import {
|
|||
handleCallScreenOn,
|
||||
handleCallScreenOff, handleCallUserRaiseHand, handleCallUserUnraiseHand,
|
||||
} from '@mmproducts/calls/store/actions/websockets';
|
||||
import {appsConfiguredAsEnabled} from '@utils/apps';
|
||||
import {getChannelSinceValue} from '@utils/channels';
|
||||
import {semverFromServerVersion} from '@utils/general';
|
||||
import websocketClient from '@websocket';
|
||||
|
||||
import {handleRefreshAppsBindings} from './apps';
|
||||
import {handleAppsPluginDisabled, handleAppsPluginEnabled, handleRefreshAppsBindings} from './apps';
|
||||
import {handleSidebarCategoryCreated, handleSidebarCategoryDeleted, handleSidebarCategoryOrderUpdated, handleSidebarCategoryUpdated} from './categories';
|
||||
import {
|
||||
handleChannelConvertedEvent,
|
||||
|
|
@ -158,6 +160,10 @@ export function doReconnect(now: number) {
|
|||
setChannelRetryFailed(false),
|
||||
], 'BATCH_WS_SUCCESS'));
|
||||
|
||||
if (appsConfiguredAsEnabled(state)) {
|
||||
dispatch(refreshAppBindings());
|
||||
}
|
||||
|
||||
try {
|
||||
const {data: me}: any = await dispatch(loadMe(null, null, true));
|
||||
|
||||
|
|
@ -430,6 +436,10 @@ function handleEvent(msg: WebSocketMessage) {
|
|||
return dispatch(handleThreadFollowChanged(msg));
|
||||
case WebsocketEvents.APPS_FRAMEWORK_REFRESH_BINDINGS:
|
||||
return dispatch(handleRefreshAppsBindings());
|
||||
case WebsocketEvents.APPS_FRAMEWORK_PLUGIN_ENABLED:
|
||||
return dispatch(handleAppsPluginEnabled());
|
||||
case WebsocketEvents.APPS_FRAMEWORK_PLUGIN_DISABLED:
|
||||
return dispatch(handleAppsPluginDisabled());
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_CREATED:
|
||||
return dispatch(handleSidebarCategoryCreated(msg));
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_UPDATED:
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
const channel2 = TestHelper.fakeChannelWithId(team.id);
|
||||
const cMember1 = TestHelper.fakeChannelMember(me.id, channel1.id);
|
||||
const cMember2 = TestHelper.fakeChannelMember(me.id, channel2.id);
|
||||
const post1 = TestHelper.fakePost(channel1.id);
|
||||
|
||||
const currentTeamId = team.id;
|
||||
const currentUserId = me.id;
|
||||
|
|
@ -92,7 +93,9 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
const initialState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
config: {
|
||||
FeatureFlagAppsEnabled: 'false',
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId,
|
||||
|
|
@ -106,7 +109,7 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
channels: {
|
||||
currentChannelId,
|
||||
channels: {
|
||||
currentChannelId: channel1,
|
||||
currentChannelId: channel1.id,
|
||||
},
|
||||
},
|
||||
users: {
|
||||
|
|
@ -119,8 +122,20 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
myPreferences: {},
|
||||
},
|
||||
posts: {
|
||||
posts: {},
|
||||
postsInChannel: {},
|
||||
posts: {
|
||||
[post1.id]: post1,
|
||||
},
|
||||
postsInChannel: {
|
||||
[channel1.id]: [
|
||||
{
|
||||
recent: true,
|
||||
order: [post1.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
websocket: {
|
||||
|
|
@ -128,6 +143,13 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
lastConnectAt: 0,
|
||||
lastDisconnectAt: 0,
|
||||
},
|
||||
views: {
|
||||
channel: {
|
||||
lastGetPosts: {
|
||||
[channel1.id]: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -170,14 +192,12 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
await TestHelper.tearDown();
|
||||
});
|
||||
|
||||
it('handle doReconnect', async () => {
|
||||
it('handle doReconnect base case', async () => {
|
||||
const state = {...initialState};
|
||||
const testStore = await mockStore(state);
|
||||
const timestamp = 1000;
|
||||
const expectedActions = [
|
||||
'BATCH_WS_SUCCESS',
|
||||
];
|
||||
const expectedMissingActions = [
|
||||
'BATCH_WS_RECONNECT',
|
||||
];
|
||||
|
||||
|
|
@ -197,7 +217,6 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
await TestHelper.wait(300);
|
||||
const actionTypes = testStore.getActions().map((a) => a.type);
|
||||
expect(actionTypes).toEqual(expectedActions);
|
||||
expect(actionTypes).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
||||
});
|
||||
|
||||
it('handle doReconnect after the current channel was archived or the user left it', async () => {
|
||||
|
|
@ -260,8 +279,6 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
const timestamp = 1000;
|
||||
const expectedActions = [
|
||||
'BATCH_WS_SUCCESS',
|
||||
];
|
||||
const expectedMissingActions = [
|
||||
'BATCH_WS_RECONNECT',
|
||||
];
|
||||
|
||||
|
|
@ -282,7 +299,6 @@ describe('Actions.Websocket doReconnect', () => {
|
|||
|
||||
const actions = testStore.getActions().map((a) => a.type);
|
||||
expect(actions).toEqual(expect.arrayContaining(expectedActions));
|
||||
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
||||
});
|
||||
|
||||
it('handle doReconnect after the current channel was archived and setting is off', async () => {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ describe('AppCommandParser', () => {
|
|||
bindings,
|
||||
threadBindings: bindings,
|
||||
threadBindingsForms: {},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ const makeStore = async (bindings: AppBinding[]) => {
|
|||
bindingsForms: {},
|
||||
threadBindings: bindings,
|
||||
threadBindingsForms: {},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ const makeStore = async (bindings: AppBinding[]) => {
|
|||
...reduxTestState,
|
||||
entities: {
|
||||
...reduxTestState.entities,
|
||||
apps: {bindings},
|
||||
apps: {
|
||||
bindings,
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const testStore = await mockStore(initialState);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ const WebsocketEvents = {
|
|||
THREAD_FOLLOW_CHANGED: 'thread_follow_changed',
|
||||
THREAD_READ_CHANGED: 'thread_read_changed',
|
||||
APPS_FRAMEWORK_REFRESH_BINDINGS: 'custom_com.mattermost.apps_refresh_bindings',
|
||||
APPS_FRAMEWORK_PLUGIN_ENABLED: 'custom_com.mattermost.apps_plugin_enabled',
|
||||
APPS_FRAMEWORK_PLUGIN_DISABLED: 'custom_com.mattermost.apps_plugin_disabled',
|
||||
SIDEBAR_CATEGORY_CREATED: 'sidebar_category_created',
|
||||
SIDEBAR_CATEGORY_UPDATED: 'sidebar_category_updated',
|
||||
SIDEBAR_CATEGORY_DELETED: 'sidebar_category_deleted',
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import keyMirror from '@mm-redux/utils/key_mirror';
|
|||
export default keyMirror({
|
||||
RECEIVED_APP_BINDINGS: null,
|
||||
RECEIVED_THREAD_APP_BINDINGS: null,
|
||||
CLEAR_APP_BINDINGS: null,
|
||||
FAILED_TO_FETCH_APP_BINDINGS: null,
|
||||
CLEAR_THREAD_APP_BINDINGS: null,
|
||||
RECEIVED_APP_COMMAND_FORM: null,
|
||||
RECEIVED_APP_RHS_COMMAND_FORM: null,
|
||||
APPS_PLUGIN_ENABLED: null,
|
||||
APPS_PLUGIN_DISABLED: null,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,20 +3,38 @@
|
|||
|
||||
import {Client4} from '@client/rest';
|
||||
import {AppsTypes} from '@mm-redux/action_types';
|
||||
import {getThreadAppsBindingsChannelId} from '@mm-redux/selectors/entities/apps';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentChannelId, getCurrentTeamId, getCurrentUserId} from '@mm-redux/selectors/entities/common';
|
||||
import {ActionFunc, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
|
||||
import {bindClientFunc} from './helpers';
|
||||
|
||||
export function refreshAppBindings(): ActionFunc {
|
||||
return (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const state = getState();
|
||||
|
||||
dispatch(fetchAppBindings(getCurrentUserId(state), getCurrentChannelId(state)));
|
||||
|
||||
const threadChannelID = getThreadAppsBindingsChannelId(state);
|
||||
if (threadChannelID) {
|
||||
dispatch(fetchThreadAppBindings(getCurrentUserId(state), threadChannelID));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAppBindings(userID: string, channelID: string): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const channel = getChannel(getState(), channelID);
|
||||
const teamID = channel?.team_id || '';
|
||||
const state = getState();
|
||||
const channel = getChannel(state, channelID);
|
||||
const teamID = channel?.team_id || getCurrentTeamId(state);
|
||||
|
||||
return dispatch(bindClientFunc({
|
||||
clientFunc: () => Client4.getAppsBindings(userID, channelID, teamID),
|
||||
onSuccess: AppsTypes.RECEIVED_APP_BINDINGS,
|
||||
onFailure: AppsTypes.CLEAR_APP_BINDINGS,
|
||||
onFailure: AppsTypes.FAILED_TO_FETCH_APP_BINDINGS,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
|
@ -33,6 +51,7 @@ export function fetchThreadAppBindings(userID: string, channelID: string): Actio
|
|||
},
|
||||
onSuccess: AppsTypes.RECEIVED_THREAD_APP_BINDINGS,
|
||||
onRequest: AppsTypes.CLEAR_THREAD_APP_BINDINGS,
|
||||
onFailure: AppsTypes.FAILED_TO_FETCH_APP_BINDINGS,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export function bindings(state: AppBinding[] = [], action: GenericAction): AppBi
|
|||
}
|
||||
return newBindings;
|
||||
}
|
||||
case AppsTypes.CLEAR_APP_BINDINGS:
|
||||
case AppsTypes.FAILED_TO_FETCH_APP_BINDINGS:
|
||||
case AppsTypes.APPS_PLUGIN_DISABLED:
|
||||
if (state.length > 0) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -42,12 +43,11 @@ export function bindingsForms(state: AppCommandFormMap = {}, action: GenericActi
|
|||
};
|
||||
return newState;
|
||||
}
|
||||
case AppsTypes.CLEAR_APP_BINDINGS: {
|
||||
case AppsTypes.FAILED_TO_FETCH_APP_BINDINGS:
|
||||
if (Object.keys(state).length) {
|
||||
return {};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
@ -108,10 +108,26 @@ export function threadBindingsForms(state: AppCommandFormMap = {}, action: Gener
|
|||
}
|
||||
}
|
||||
|
||||
export function pluginEnabled(state = true, action: GenericAction): boolean {
|
||||
switch (action.type) {
|
||||
case AppsTypes.APPS_PLUGIN_ENABLED:
|
||||
return true;
|
||||
case AppsTypes.APPS_PLUGIN_DISABLED:
|
||||
return false;
|
||||
case AppsTypes.RECEIVED_APP_BINDINGS:
|
||||
return true;
|
||||
case AppsTypes.FAILED_TO_FETCH_APP_BINDINGS:
|
||||
return false;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default (combineReducers({
|
||||
bindings,
|
||||
bindingsForms,
|
||||
threadBindings,
|
||||
threadBindingsForms,
|
||||
threadBindingsChannelId,
|
||||
pluginEnabled,
|
||||
}) as (b: AppsState, a: GenericAction) => AppsState);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export type AppsState = {
|
|||
threadBindings: AppBinding[];
|
||||
threadBindingsForms: AppCommandFormMap;
|
||||
threadBindingsChannelId: string;
|
||||
pluginEnabled: boolean;
|
||||
};
|
||||
|
||||
export type AppBinding = {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,19 @@ import {AppBinding, AppCall, AppCallRequest, AppCallResponse, AppCallValues, App
|
|||
import {Config} from '@mm-redux/types/config';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
|
||||
export function appsEnabled(state: GlobalState): boolean { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
export function appsConfiguredAsEnabled(state: GlobalState): boolean { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
const enabled = getConfig(state)?.['FeatureFlagAppsEnabled' as keyof Partial<Config>];
|
||||
return enabled === 'true';
|
||||
}
|
||||
|
||||
export function appsPluginIsEnabled(state: GlobalState): boolean {
|
||||
return state.entities.apps.pluginEnabled;
|
||||
}
|
||||
|
||||
export function appsEnabled(state: GlobalState): boolean {
|
||||
return appsConfiguredAsEnabled(state) && appsPluginIsEnabled(state);
|
||||
}
|
||||
|
||||
export function cleanBinding(binding: AppBinding, topLocation: string): AppBinding {
|
||||
return cleanBindingRec(binding, topLocation, 0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue