[MM-54493] Allow a user to disable the webapp prefetch (#24389)
* allow a user to disable the webapp prefetch if they are experiencing degraded performance on initial load --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -68,6 +68,8 @@ describe('/components/data_prefetch', () => {
|
|||||||
last_post_at: 1235,
|
last_post_at: 1235,
|
||||||
last_root_post_at: 1235,
|
last_root_post_at: 1235,
|
||||||
})],
|
})],
|
||||||
|
disableWebappPrefetchAllowed: false,
|
||||||
|
dataPrefetchEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ type Props = {
|
|||||||
sidebarLoaded: boolean;
|
sidebarLoaded: boolean;
|
||||||
|
|
||||||
unreadChannels: Channel[];
|
unreadChannels: Channel[];
|
||||||
|
|
||||||
|
disableWebappPrefetchAllowed: boolean;
|
||||||
|
dataPrefetchEnabled: boolean;
|
||||||
actions: {
|
actions: {
|
||||||
prefetchChannelPosts: (channelId: string, delay?: number) => Promise<any>;
|
prefetchChannelPosts: (channelId: string, delay?: number) => Promise<any>;
|
||||||
trackPreloadedChannels: (prefetchQueueObj: Record<string, string[]>) => void;
|
trackPreloadedChannels: (prefetchQueueObj: Record<string, string[]>) => void;
|
||||||
@@ -52,15 +55,20 @@ export default class DataPrefetch extends React.PureComponent<Props> {
|
|||||||
private prefetchTimeout?: number;
|
private prefetchTimeout?: number;
|
||||||
|
|
||||||
async componentDidUpdate(prevProps: Props) {
|
async componentDidUpdate(prevProps: Props) {
|
||||||
const {currentChannelId, prefetchQueueObj, sidebarLoaded} = this.props;
|
const {currentChannelId, prefetchQueueObj, sidebarLoaded, disableWebappPrefetchAllowed, dataPrefetchEnabled} = this.props;
|
||||||
|
const enablePrefetch = (!disableWebappPrefetchAllowed) || (disableWebappPrefetchAllowed && dataPrefetchEnabled);
|
||||||
if (currentChannelId && sidebarLoaded && (!prevProps.currentChannelId || !prevProps.sidebarLoaded)) {
|
if (currentChannelId && sidebarLoaded && (!prevProps.currentChannelId || !prevProps.sidebarLoaded)) {
|
||||||
queue.add(async () => this.prefetchPosts(currentChannelId));
|
queue.add(async () => this.prefetchPosts(currentChannelId));
|
||||||
await loadProfilesForSidebar();
|
await loadProfilesForSidebar();
|
||||||
this.prefetchData();
|
if (enablePrefetch) {
|
||||||
|
this.prefetchData();
|
||||||
|
}
|
||||||
} else if (prevProps.prefetchQueueObj !== prefetchQueueObj) {
|
} else if (prevProps.prefetchQueueObj !== prefetchQueueObj) {
|
||||||
clearTimeout(this.prefetchTimeout);
|
clearTimeout(this.prefetchTimeout);
|
||||||
await queue.clear();
|
await queue.clear();
|
||||||
this.prefetchData();
|
if (enablePrefetch) {
|
||||||
|
this.prefetchData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentChannelId && sidebarLoaded && (!prevProps.currentChannelId || !prevProps.sidebarLoaded)) {
|
if (currentChannelId && sidebarLoaded && (!prevProps.currentChannelId || !prevProps.sidebarLoaded)) {
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import type {Channel, ChannelMembership} from '@mattermost/types/channels';
|
|||||||
import type {PostList} from '@mattermost/types/posts';
|
import type {PostList} from '@mattermost/types/posts';
|
||||||
import type {RelationOneToOne} from '@mattermost/types/utilities';
|
import type {RelationOneToOne} from '@mattermost/types/utilities';
|
||||||
|
|
||||||
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
import {getCurrentChannelId, getUnreadChannels} from 'mattermost-redux/selectors/entities/channels';
|
import {getCurrentChannelId, getUnreadChannels} from 'mattermost-redux/selectors/entities/channels';
|
||||||
import {getMyChannelMemberships} from 'mattermost-redux/selectors/entities/common';
|
import {getMyChannelMemberships} from 'mattermost-redux/selectors/entities/common';
|
||||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
import {isPerformanceDebuggingEnabled} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {getBool, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||||
import {memoizeResult} from 'mattermost-redux/utils/helpers';
|
import {memoizeResult} from 'mattermost-redux/utils/helpers';
|
||||||
|
|
||||||
@@ -82,6 +84,7 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
const unreadChannels = getUnreadChannels(state, lastUnreadChannel);
|
const unreadChannels = getUnreadChannels(state, lastUnreadChannel);
|
||||||
const prefetchQueueObj = prefetchQueue(unreadChannels, memberships, isCollapsedThreadsEnabled(state));
|
const prefetchQueueObj = prefetchQueue(unreadChannels, memberships, isCollapsedThreadsEnabled(state));
|
||||||
const prefetchRequestStatus = state.views.channel.channelPrefetchStatus;
|
const prefetchRequestStatus = state.views.channel.channelPrefetchStatus;
|
||||||
|
const disableWebappPrefetchAllowed = isPerformanceDebuggingEnabled(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentChannelId: getCurrentChannelId(state),
|
currentChannelId: getCurrentChannelId(state),
|
||||||
@@ -89,6 +92,8 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
prefetchRequestStatus,
|
prefetchRequestStatus,
|
||||||
sidebarLoaded: isSidebarLoaded(state),
|
sidebarLoaded: isSidebarLoaded(state),
|
||||||
unreadChannels,
|
unreadChannels,
|
||||||
|
disableWebappPrefetchAllowed,
|
||||||
|
dataPrefetchEnabled: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_DATA_PREFETCH, true),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {ActionCreatorsMapObject, Dispatch} from 'redux';
|
|||||||
|
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {updateUserActive, revokeAllSessionsForUser} from 'mattermost-redux/actions/users';
|
import {updateUserActive, revokeAllSessionsForUser} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, isPerformanceDebuggingEnabled} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {get, getUnreadScrollPositionPreference, makeGetCategory, syncedDraftsAreAllowed} from 'mattermost-redux/selectors/entities/preferences';
|
import {get, getUnreadScrollPositionPreference, makeGetCategory, syncedDraftsAreAllowed} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
import type {ActionFunc} from 'mattermost-redux/types/actions';
|
import type {ActionFunc} from 'mattermost-redux/types/actions';
|
||||||
@@ -27,6 +27,7 @@ function makeMapStateToProps() {
|
|||||||
|
|
||||||
const enablePreviewFeatures = config.EnablePreviewFeatures === 'true';
|
const enablePreviewFeatures = config.EnablePreviewFeatures === 'true';
|
||||||
const enableUserDeactivation = config.EnableUserDeactivation === 'true';
|
const enableUserDeactivation = config.EnableUserDeactivation === 'true';
|
||||||
|
const disableWebappPrefetchAllowed = isPerformanceDebuggingEnabled(state);
|
||||||
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
|
const enableJoinLeaveMessage = config.EnableJoinLeaveMessageByDefault === 'true';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -41,6 +42,8 @@ function makeMapStateToProps() {
|
|||||||
enablePreviewFeatures,
|
enablePreviewFeatures,
|
||||||
enableUserDeactivation,
|
enableUserDeactivation,
|
||||||
syncedDraftsAreAllowed: syncedDraftsAreAllowed(state),
|
syncedDraftsAreAllowed: syncedDraftsAreAllowed(state),
|
||||||
|
disableWebappPrefetchAllowed,
|
||||||
|
dataPrefetchEnabled: get(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'data_prefetch', 'true'),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
|||||||
enablePreviewFeatures: false,
|
enablePreviewFeatures: false,
|
||||||
enableUserDeactivation: false,
|
enableUserDeactivation: false,
|
||||||
syncedDraftsAreAllowed: true,
|
syncedDraftsAreAllowed: true,
|
||||||
|
disableWebappPrefetchAllowed: false,
|
||||||
|
dataPrefetchEnabled: 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should have called handleSubmit', async () => {
|
test('should have called handleSubmit', async () => {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Settings = {
|
|||||||
formatting: Props['formatting'];
|
formatting: Props['formatting'];
|
||||||
join_leave: Props['joinLeave'];
|
join_leave: Props['joinLeave'];
|
||||||
sync_drafts: Props['syncDrafts'];
|
sync_drafts: Props['syncDrafts'];
|
||||||
|
data_prefetch: Props['dataPrefetchEnabled'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
@@ -54,6 +55,8 @@ export type Props = {
|
|||||||
enablePreviewFeatures: boolean;
|
enablePreviewFeatures: boolean;
|
||||||
enableUserDeactivation: boolean;
|
enableUserDeactivation: boolean;
|
||||||
syncedDraftsAreAllowed: boolean;
|
syncedDraftsAreAllowed: boolean;
|
||||||
|
disableWebappPrefetchAllowed: boolean;
|
||||||
|
dataPrefetchEnabled: string;
|
||||||
actions: {
|
actions: {
|
||||||
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
||||||
updateUserActive: (userId: string, active: boolean) => Promise<ActionResult>;
|
updateUserActive: (userId: string, active: boolean) => Promise<ActionResult>;
|
||||||
@@ -87,6 +90,7 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
formatting: this.props.formatting,
|
formatting: this.props.formatting,
|
||||||
join_leave: this.props.joinLeave,
|
join_leave: this.props.joinLeave,
|
||||||
sync_drafts: this.props.syncDrafts,
|
sync_drafts: this.props.syncDrafts,
|
||||||
|
data_prefetch: this.props.dataPrefetchEnabled,
|
||||||
[Preferences.UNREAD_SCROLL_POSITION]: this.props.unreadScrollPosition,
|
[Preferences.UNREAD_SCROLL_POSITION]: this.props.unreadScrollPosition,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -575,6 +579,93 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderDataPrefetchSection = () => {
|
||||||
|
const active = this.props.activeSection === AdvancedSections.DATA_PREFETCH;
|
||||||
|
let max = null;
|
||||||
|
if (active) {
|
||||||
|
max = (
|
||||||
|
<SettingItemMax
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.advance.dataPrefetch.Title'
|
||||||
|
defaultMessage='Allow Mattermost to prefetch channel posts'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
inputs={[
|
||||||
|
<fieldset key='syncDraftsSetting'>
|
||||||
|
<legend className='form-legend hidden-label'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.advance.dataPrefetch.Title'
|
||||||
|
defaultMessage='Allow Mattermost to prefetch channel posts'
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div className='radio'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
id='dataPrefetchOn'
|
||||||
|
type='radio'
|
||||||
|
name='dataPrefetch'
|
||||||
|
checked={this.state.settings.data_prefetch !== 'false'}
|
||||||
|
onChange={this.updateSetting.bind(this, 'data_prefetch', 'true')}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.advance.on'
|
||||||
|
defaultMessage='On'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<div className='radio'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
id='dataPrefetchOff'
|
||||||
|
type='radio'
|
||||||
|
name='dataPrefetch'
|
||||||
|
checked={this.state.settings.data_prefetch === 'false'}
|
||||||
|
onChange={this.updateSetting.bind(this, 'data_prefetch', 'false')}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.advance.off'
|
||||||
|
defaultMessage='Off'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.advance.dataPrefetch.Desc'
|
||||||
|
defaultMessage='When disabled, messages and user information will be fetched on each channel load instead of being pre-fetched on startup. Disabling prefetch is recommended for users with a high unread channel count in order to improve application performance.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>,
|
||||||
|
]}
|
||||||
|
setting={AdvancedSections.DATA_PREFETCH}
|
||||||
|
submit={this.handleSubmit.bind(this, ['data_prefetch'])}
|
||||||
|
saving={this.state.isSaving}
|
||||||
|
serverError={this.state.serverError}
|
||||||
|
updateSection={this.handleUpdateSection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItem
|
||||||
|
active={active}
|
||||||
|
areAllSectionsInactive={this.props.activeSection === ''}
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.advance.dataPrefetch.Title'
|
||||||
|
defaultMessage='Allow Mattermost to prefetch channel posts'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
describe={this.renderOnOffLabel(this.state.settings.data_prefetch)}
|
||||||
|
section={AdvancedSections.DATA_PREFETCH}
|
||||||
|
updateSection={this.handleUpdateSection}
|
||||||
|
max={max}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
renderFeatureLabel(feature: string): ReactNode {
|
renderFeatureLabel(feature: string): ReactNode {
|
||||||
switch (feature) {
|
switch (feature) {
|
||||||
case 'MARKDOWN_PREVIEW':
|
case 'MARKDOWN_PREVIEW':
|
||||||
@@ -895,6 +986,15 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dataPrefetchSection = null;
|
||||||
|
let dataPrefetchSectionDivider = null;
|
||||||
|
if (this.props.disableWebappPrefetchAllowed) {
|
||||||
|
dataPrefetchSection = this.renderDataPrefetchSection();
|
||||||
|
if (syncDraftsSection) {
|
||||||
|
dataPrefetchSectionDivider = <div className='divider-light'/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
@@ -953,6 +1053,8 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
|
|||||||
{unreadScrollPositionSection}
|
{unreadScrollPositionSection}
|
||||||
{syncDraftsSectionDivider}
|
{syncDraftsSectionDivider}
|
||||||
{syncDraftsSection}
|
{syncDraftsSection}
|
||||||
|
{dataPrefetchSectionDivider}
|
||||||
|
{dataPrefetchSection}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
{makeConfirmationModal}
|
{makeConfirmationModal}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5208,6 +5208,8 @@
|
|||||||
"user_profile.send.dm.yourself": "Send yourself a message",
|
"user_profile.send.dm.yourself": "Send yourself a message",
|
||||||
"user.settings.advance.confirmDeactivateAccountTitle": "Confirm Deactivation",
|
"user.settings.advance.confirmDeactivateAccountTitle": "Confirm Deactivation",
|
||||||
"user.settings.advance.confirmDeactivateDesc": "Are you sure you want to deactivate your account? This can only be reversed by your System Administrator.",
|
"user.settings.advance.confirmDeactivateDesc": "Are you sure you want to deactivate your account? This can only be reversed by your System Administrator.",
|
||||||
|
"user.settings.advance.dataPrefetch.Desc": "When disabled, messages and user information will be fetched on each channel load instead of being pre-fetched on startup. Disabling prefetch is recommended for users with a high unread channel count in order to improve application performance.",
|
||||||
|
"user.settings.advance.dataPrefetch.Title": "Allow Mattermost to prefetch channel posts",
|
||||||
"user.settings.advance.deactivate_member_modal.deactivateButton": "Yes, deactivate my account",
|
"user.settings.advance.deactivate_member_modal.deactivateButton": "Yes, deactivate my account",
|
||||||
"user.settings.advance.deactivateAccountTitle": "Deactivate Account",
|
"user.settings.advance.deactivateAccountTitle": "Deactivate Account",
|
||||||
"user.settings.advance.deactivateDesc": "Deactivating your account removes your ability to log in to this server and disables all email and mobile notifications. To reactivate your account, contact your System Administrator.",
|
"user.settings.advance.deactivateDesc": "Deactivating your account removes your ability to log in to this server and disables all email and mobile notifications. To reactivate your account, contact your System Administrator.",
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const Preferences = {
|
|||||||
ADVANCED_CODE_BLOCK_ON_CTRL_ENTER: 'code_block_ctrl_enter',
|
ADVANCED_CODE_BLOCK_ON_CTRL_ENTER: 'code_block_ctrl_enter',
|
||||||
ADVANCED_SEND_ON_CTRL_ENTER: 'send_on_ctrl_enter',
|
ADVANCED_SEND_ON_CTRL_ENTER: 'send_on_ctrl_enter',
|
||||||
ADVANCED_SYNC_DRAFTS: 'sync_drafts',
|
ADVANCED_SYNC_DRAFTS: 'sync_drafts',
|
||||||
|
ADVANCED_DATA_PREFETCH: 'data_prefetch',
|
||||||
CATEGORY_WHATS_NEW_MODAL: 'whats_new_modal',
|
CATEGORY_WHATS_NEW_MODAL: 'whats_new_modal',
|
||||||
HAS_SEEN_SIDEBAR_WHATS_NEW_MODAL: 'has_seen_sidebar_whats_new_modal',
|
HAS_SEEN_SIDEBAR_WHATS_NEW_MODAL: 'has_seen_sidebar_whats_new_modal',
|
||||||
|
|
||||||
|
|||||||
@@ -1014,6 +1014,7 @@ export const AdvancedSections = {
|
|||||||
PREVIEW_FEATURES: 'advancedPreviewFeatures',
|
PREVIEW_FEATURES: 'advancedPreviewFeatures',
|
||||||
PERFORMANCE_DEBUGGING: 'performanceDebugging',
|
PERFORMANCE_DEBUGGING: 'performanceDebugging',
|
||||||
SYNC_DRAFTS: 'syncDrafts',
|
SYNC_DRAFTS: 'syncDrafts',
|
||||||
|
DATA_PREFETCH: 'dataPrefetch',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RHSStates = {
|
export const RHSStates = {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user