Deprecate admin advisor (#26045)

* Deprecate admin advisor

* Webapp portion

* More webapp deprecation

* More cleanup

* Linting

* emoved metric ack dialog from annoucenemet bar

* Cleanued up uninsed i18n strings

* Updated test

* fixed types

* Updating server test

* Updated i18n

* Updated cypress test:

* Updated cypress test:

---------

Co-authored-by: harshil Sharma <harshilsharma63@gmail.com>
This commit is contained in:
Maria A Nunez
2024-02-25 22:35:00 -05:00
committed by GitHub
parent dc8fc773dc
commit e9b9d4ff60
42 changed files with 45 additions and 2285 deletions

View File

@@ -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, StoragePrefixes} from 'utils/constants';
import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, PageLoadContext, StoragePrefixes} from 'utils/constants';
import {getSiteURL} from 'utils/url';
import {temporarilySetPageLoadContext} from './telemetry_actions';
@@ -527,14 +527,6 @@ export function handleEvent(msg) {
handleGroupNotAssociatedToChannelEvent(msg);
break;
case SocketEvents.WARN_METRIC_STATUS_RECEIVED:
handleWarnMetricStatusReceivedEvent(msg);
break;
case SocketEvents.WARN_METRIC_STATUS_REMOVED:
handleWarnMetricStatusRemovedEvent(msg);
break;
case SocketEvents.SIDEBAR_CATEGORY_CREATED:
dispatch(handleSidebarCategoryCreated(msg));
break;
@@ -1481,30 +1473,6 @@ function handleGroupNotAssociatedToChannelEvent(msg) {
});
}
function handleWarnMetricStatusReceivedEvent(msg) {
var receivedData = JSON.parse(msg.data.warnMetricStatus);
let bannerData;
if (receivedData.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
bannerData = AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS;
} else if (receivedData.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
bannerData = AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS;
}
store.dispatch(batchActions([
{
type: GeneralTypes.WARN_METRIC_STATUS_RECEIVED,
data: receivedData,
},
{
type: ActionTypes.SHOW_NOTICE,
data: [bannerData],
},
]));
}
function handleWarnMetricStatusRemovedEvent(msg) {
store.dispatch({type: GeneralTypes.WARN_METRIC_STATUS_REMOVED, data: {id: msg.data.warnMetricId}});
}
function handleSidebarCategoryCreated(msg) {
return (doDispatch, doGetState) => {
const state = doGetState();

View File

@@ -17,10 +17,9 @@ import {trackEvent} from 'actions/telemetry_actions';
import PurchaseLink from 'components/announcement_bar/purchase_link/purchase_link';
import ExternalLink from 'components/external_link';
import ackIcon from 'images/icons/check-circle-outline.svg';
import alertIcon from 'images/icons/round-white-info-icon.svg';
import warningIcon from 'images/icons/warning-icon.svg';
import {AnnouncementBarTypes, AnnouncementBarMessages, WarnMetricTypes, Preferences, ConfigurationBanners, Constants, TELEMETRY_CATEGORIES} from 'utils/constants';
import {AnnouncementBarTypes, AnnouncementBarMessages, Preferences, ConfigurationBanners, Constants, TELEMETRY_CATEGORIES} from 'utils/constants';
import {t} from 'utils/i18n';
import {daysToLicenseExpire, isLicenseExpired, isLicenseExpiring, isLicensePastGracePeriod, isTrialLicense} from 'utils/license_utils';
import {getSkuDisplayName} from 'utils/subscription';
@@ -76,115 +75,8 @@ const ConfigurationAnnouncementBar = (props: Props) => {
props.actions.dismissNotice(AnnouncementBarMessages.TRIAL_LICENSE_EXPIRING);
};
const dismissNumberOfActiveUsersWarnMetric = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS);
};
const dismissNumberOfPostsWarnMetric = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS);
};
const dismissNumberOfActiveUsersWarnMetricAck = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS_ACK);
};
const dismissNumberOfPostsWarnMetricAck = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS_ACK);
};
const renewLinkTelemetry = {success: 'renew_license_banner_success', error: 'renew_license_banner_fail'};
const getNoticeForWarnMetric = (warnMetricStatus: any) => {
if (!warnMetricStatus ||
(warnMetricStatus.id !== WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500 &&
warnMetricStatus.id !== WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M)) {
return null;
}
let message: JSX.Element | string = '';
let type = '';
let showModal = false;
let dismissFunc;
let isDismissed = null;
let canCloseBar = false;
if (warnMetricStatus.acked) {
message = (
<>
<img
className='advisor-icon'
src={ackIcon}
/>
<FormattedMessage
id='announcement_bar.warn_metric_status_ack.text'
defaultMessage='Thank you for contacting Mattermost. We will follow up with you soon.'
/>
</>
);
if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
dismissFunc = dismissNumberOfActiveUsersWarnMetricAck;
isDismissed = props.dismissedNumberOfActiveUsersWarnMetricStatusAck;
} else if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
dismissFunc = dismissNumberOfPostsWarnMetricAck;
isDismissed = props.dismissedNumberOfPostsWarnMetricStatusAck;
}
type = AnnouncementBarTypes.ADVISOR_ACK;
showModal = false;
canCloseBar = true;
} else {
if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
message = (
<>
<img
className='advisor-icon'
src={alertIcon}
/>
<FormattedMessage
id='announcement_bar.number_active_users_warn_metric_status.text'
defaultMessage='You now have over {limit} users. We strongly recommend using advanced features for large-scale servers.'
values={{
limit: warnMetricStatus.limit,
}}
/>
</>
);
dismissFunc = dismissNumberOfActiveUsersWarnMetric;
isDismissed = props.dismissedNumberOfActiveUsersWarnMetricStatus;
} else if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
message = (
<>
<img
className='advisor-icon'
src={alertIcon}
/>
<FormattedMessage
id='announcement_bar.number_of_posts_warn_metric_status.text'
defaultMessage='You now have over {limit} posts. We strongly recommend using advanced features for large-scale servers.'
values={{
limit: warnMetricStatus.limit,
}}
/>
</>
);
dismissFunc = dismissNumberOfPostsWarnMetric;
isDismissed = props.dismissedNumberOfPostsWarnMetricStatus;
}
type = AnnouncementBarTypes.ADVISOR;
showModal = true;
canCloseBar = false;
}
return {
Message: message,
DismissFunc: dismissFunc,
IsDismissed: isDismissed,
Type: type,
ShowModal: showModal,
CanCloseBar: canCloseBar,
};
};
// System administrators
if (props.canViewSystemErrors) {
if ((isLicensePastGracePeriod(props.license) || isLicenseExpired(props.license)) && !props.dismissedExpiredLicense) {
@@ -316,29 +208,6 @@ const ConfigurationAnnouncementBar = (props: Props) => {
/>
);
}
if (props.license?.IsLicensed === 'false' &&
props.warnMetricsStatus) {
for (const status of Object.values(props.warnMetricsStatus)) {
const notice = getNoticeForWarnMetric(status);
if (!notice || notice.IsDismissed) {
continue;
}
return (
<AnnouncementBar
showCloseButton={notice.CanCloseBar}
handleClose={notice.DismissFunc}
type={notice.Type}
showModal={notice.ShowModal}
modalButtonText={t('announcement_bar.error.warn_metric_status.link')}
modalButtonDefaultText='Learn more'
warnMetricStatus={status}
message={notice.Message}
/>
);
}
}
} else {
// Regular users
if (isLicensePastGracePeriod(props.license)) { //eslint-disable-line no-lonely-if

View File

@@ -25,10 +25,6 @@ function mapStateToProps(state: GlobalState) {
dismissedExpiringTrialLicense: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.TRIAL_LICENSE_EXPIRING]),
dismissedExpiredLicense: Boolean(getPreference(state, Preferences.CONFIGURATION_BANNERS, ConfigurationBanners.LICENSE_EXPIRED) === 'true'),
dismissedExpiringLicense: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.LICENSE_EXPIRING]),
dismissedNumberOfActiveUsersWarnMetricStatus: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS]),
dismissedNumberOfActiveUsersWarnMetricStatusAck: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS_ACK]),
dismissedNumberOfPostsWarnMetricStatus: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS]),
dismissedNumberOfPostsWarnMetricStatusAck: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS_ACK]),
currentUserId,
};
}

View File

@@ -5,17 +5,11 @@ import type {ReactNode} from 'react';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import type {WarnMetricStatus} from '@mattermost/types/config';
import {trackEvent} from 'actions/telemetry_actions.jsx';
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import OverlayTrigger from 'components/overlay_trigger';
import ToggleModalButton from 'components/toggle_modal_button';
import Tooltip from 'components/tooltip';
import WarnMetricAckModal from 'components/warn_metric_ack_modal';
import {Constants, AnnouncementBarTypes, ModalIdentifiers} from 'utils/constants';
import {Constants, AnnouncementBarTypes} from 'utils/constants';
import {isStringContainingUrl} from 'utils/url';
type Props = {
@@ -34,7 +28,6 @@ type Props = {
modalButtonDefaultText?: string;
showLinkAsButton: boolean;
icon?: ReactNode;
warnMetricStatus?: WarnMetricStatus;
actions: {
incrementAnnouncementBarCount: () => void;
decrementAnnouncementBarCount: () => void;
@@ -191,33 +184,6 @@ export default class AnnouncementBar extends React.PureComponent<Props, State> {
>
{message}
</span>
{
!this.props.showLinkAsButton && this.props.showCTA && this.props.modalButtonText && this.props.modalButtonDefaultText &&
<span className='announcement-bar__link'>
{this.props.showModal &&
<FormattedMessage
id={this.props.modalButtonText}
defaultMessage={this.props.modalButtonDefaultText}
>
{(linkmessage) => (
<ToggleModalButton
ariaLabel={linkmessage as unknown as string}
className={'color--link--adminack'}
dialogType={WarnMetricAckModal}
onClick={() => trackEvent('admin', 'click_warn_metric_learn_more')}
modalId={ModalIdentifiers.WARN_METRIC_ACK}
dialogProps={{
warnMetricStatus: this.props.warnMetricStatus,
closeParentComponent: this.props.handleClose,
}}
>
{linkmessage}
</ToggleModalButton>
)}
</FormattedMessage>
}
</span>
}
{
this.props.showLinkAsButton && this.props.showCTA && this.props.modalButtonText && this.props.modalButtonDefaultText &&
<button

View File

@@ -9,7 +9,7 @@ import {getStandardAnalytics} from 'mattermost-redux/actions/admin';
import {getCloudSubscription, getCloudCustomer} from 'mattermost-redux/actions/cloud';
import {dismissError} from 'mattermost-redux/actions/errors';
import {Permissions} from 'mattermost-redux/constants';
import {getConfig, getLicense, warnMetricsStatus as getWarnMetricsStatus} from 'mattermost-redux/selectors/entities/general';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles';
import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
import {getDisplayableErrors} from 'mattermost-redux/selectors/errors';
@@ -25,7 +25,6 @@ function mapStateToProps(state: GlobalState) {
const license = getLicense(state);
const config = getConfig(state);
const errors = getDisplayableErrors(state);
const warnMetricsStatus = getWarnMetricsStatus(state);
const isCloud = license.Cloud === 'true';
const subscription = state.entities.cloud?.subscription;
const userIsAdmin = isCurrentUserSystemAdmin(state);
@@ -40,7 +39,6 @@ function mapStateToProps(state: GlobalState) {
config,
canViewSystemErrors,
latestError,
warnMetricsStatus,
isCloud,
subscription,
userIsAdmin,

View File

@@ -1,223 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/WarnMetricAckModal error display 1`] = `
<Modal
animation={true}
aria-labelledby="warnMetricAckHeaderModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={false}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={false}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h1"
id="warnMetricAckHeaderModalLabel"
/>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div>
<br />
<div
className="form-group has-error"
>
<br />
<label
className="control-label"
>
<MemoizedFormattedMessage
defaultMessage="Support could not be reached. Please {link}."
id="warn_metric_ack_modal.mailto.message"
values={
Object {
"link": <WarnMetricAckErrorLink
defaultMessage="email us"
forceAck={true}
messageId="warn_metric_ack_modal.mailto.link"
onClickHandler={[Function]}
url="mailto:support-advisor@mattermost.com?cc=a@test.com&subject=Mattermost%20Contact%20Us%20request&body=Mattermost%20Contact%20Us%20request.%0D%0AContact%20Fake%20Person%0D%0AEmail%20a%40test.com%0D%0ASite%20URL%20http%3A%2F%2Flocalhost%3A8065%0D%0ATelemetry%20Id%20diag_0%0D%0AIf%20you%20have%20any%20additional%20inquiries%2C%20please%20contact%20support%40mattermost.com"
/>,
}
}
/>
</label>
</div>
<br />
<div
className="help__format-text"
style={
Object {
"display": "flex",
"flexWrap": "wrap",
"opacity": "0.56",
}
}
>
<MemoizedFormattedMessage
defaultMessage="By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}"
id="warn_metric_ack_modal.subtext"
values={
Object {
"link": <ErrorLink
defaultMessage="Learn more"
messageId="warn_metric_ack_modal.learn_more.link"
url="https://mattermost.com/pl/default-admin-advisory"
/>,
}
}
/>
</div>
</div>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
>
<button
autoFocus={true}
className="btn btn-primary save-button"
data-dismiss="modal"
disabled={false}
onClick={[Function]}
>
<Memo(LoadingWrapper)
loading={false}
text="Sending email"
>
<MemoizedFormattedMessage
defaultMessage="Acknowledge"
id="warn_metric_ack_modal.contact_support"
/>
</Memo(LoadingWrapper)>
</button>
</ModalFooter>
</Modal>
`;
exports[`components/WarnMetricAckModal should match snapshot, init 1`] = `
<Modal
animation={true}
aria-labelledby="warnMetricAckHeaderModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={false}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={false}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h1"
id="warnMetricAckHeaderModalLabel"
/>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div>
<br />
<br />
<div
className="help__format-text"
style={
Object {
"display": "flex",
"flexWrap": "wrap",
"opacity": "0.56",
}
}
>
<MemoizedFormattedMessage
defaultMessage="By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}"
id="warn_metric_ack_modal.subtext"
values={
Object {
"link": <ErrorLink
defaultMessage="Learn more"
messageId="warn_metric_ack_modal.learn_more.link"
url="https://mattermost.com/pl/default-admin-advisory"
/>,
}
}
/>
</div>
</div>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
>
<button
autoFocus={true}
className="btn btn-primary save-button"
data-dismiss="modal"
disabled={false}
onClick={[Function]}
>
<Memo(LoadingWrapper)
loading={false}
text="Sending email"
>
<MemoizedFormattedMessage
defaultMessage="Acknowledge"
id="warn_metric_ack_modal.contact_support"
/>
</Memo(LoadingWrapper)>
</button>
</ModalFooter>
</Modal>
`;

View File

@@ -1,52 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux';
import {sendWarnMetricAck} from 'mattermost-redux/actions/admin';
import {getFilteredUsersStats} from 'mattermost-redux/actions/users';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getFilteredUsersStats as selectFilteredUserStats} from 'mattermost-redux/selectors/entities/users';
import {closeModal} from 'actions/views/modals';
import {isModalOpen} from 'selectors/views/modals';
import {ModalIdentifiers} from 'utils/constants';
import type {GlobalState} from 'types/store';
import WarnMetricAckModal from './warn_metric_ack_modal';
type Props = {
closeParentComponent: () => Promise<void>;
};
function mapStateToProps(state: GlobalState, ownProps: Props) {
const config = getConfig(state);
return {
totalUsers: selectFilteredUserStats(state)?.total_users_count || 0,
user: getCurrentUser(state),
telemetryId: config.DiagnosticId,
show: isModalOpen(state, ModalIdentifiers.WARN_METRIC_ACK),
closeParentComponent: ownProps.closeParentComponent,
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators(
{
closeModal,
sendWarnMetricAck,
getFilteredUsersStats,
},
dispatch,
),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(WarnMetricAckModal);

View File

@@ -1,100 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Modal} from 'react-bootstrap';
import type {UserProfile} from '@mattermost/types/users';
import WarnMetricAckModal from 'components/warn_metric_ack_modal/warn_metric_ack_modal';
describe('components/WarnMetricAckModal', () => {
const serverError = 'some error';
const baseProps = {
stats: {
registered_users: 200,
},
user: {
id: 'someUserId',
first_name: 'Fake',
last_name: 'Person',
email: 'a@test.com',
} as UserProfile,
show: false,
telemetryId: 'diag_0',
closeParentComponent: jest.fn(),
warnMetricStatus: {
id: 'metric1',
limit: 500,
acked: false,
store_status: 'status1',
},
actions: {
closeModal: jest.fn(),
getFilteredUsersStats: jest.fn(),
sendWarnMetricAck: jest.fn().mockResolvedValue({}),
},
};
test('should match snapshot, init', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('error display', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
wrapper.setState({serverError});
expect(wrapper).toMatchSnapshot();
});
test('should match state when onHide is called', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
wrapper.setState({saving: true});
wrapper.instance().onHide();
expect(wrapper.state('saving')).toEqual(false);
});
test('should match state when onHideWithParent is called', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
wrapper.setState({saving: true});
wrapper.instance().onHide();
expect(baseProps.closeParentComponent).toHaveBeenCalledTimes(1);
expect(wrapper.state('saving')).toEqual(false);
});
test('send ack on acknowledge button click', () => {
const props = {...baseProps};
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...props}/>,
);
wrapper.setState({saving: false});
wrapper.find('.save-button').simulate('click');
expect(props.actions.sendWarnMetricAck).toHaveBeenCalledTimes(1);
});
test('should have called props.onHide when Modal.onExited is called', () => {
const props = {...baseProps};
const wrapper = shallow(
<WarnMetricAckModal {...props}/>,
);
wrapper.find(Modal).props().onExited!(document.createElement('div'));
expect(baseProps.actions.closeModal).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,305 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {CSSProperties} from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import type {WarnMetricStatus} from '@mattermost/types/config';
import type {ServerError} from '@mattermost/types/errors';
import type {GetFilteredUsersStatsOpts, UsersStats, UserProfile} from '@mattermost/types/users';
import type {ActionResult} from 'mattermost-redux/types/actions';
import {trackEvent} from 'actions/telemetry_actions';
import ErrorLink from 'components/error_page/error_link';
import ExternalLink from 'components/external_link';
import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
import {ModalIdentifiers, WarnMetricTypes} from 'utils/constants';
import {t} from 'utils/i18n';
import {getSiteURL} from 'utils/url';
import * as Utils from 'utils/utils';
type Props = {
user: UserProfile;
telemetryId?: string;
show: boolean;
closeParentComponent?: () => Promise<void>;
totalUsers?: number;
warnMetricStatus: WarnMetricStatus;
actions: {
closeModal: (modalId: string) => void;
sendWarnMetricAck: (warnMetricId: string, forceAck: boolean) => Promise<ActionResult>;
getFilteredUsersStats: (filters: GetFilteredUsersStatsOpts) => Promise<{
data?: UsersStats;
error?: ServerError;
}>;
};
}
type State = {
serverError: string | null;
gettingTrial: boolean;
gettingTrialError: string | null;
saving: boolean;
}
const containerStyles: CSSProperties = {
display: 'flex',
opacity: '0.56',
flexWrap: 'wrap',
};
export default class WarnMetricAckModal extends React.PureComponent<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
saving: false,
serverError: null,
gettingTrial: false,
gettingTrialError: null,
};
}
componentDidMount() {
this.props.actions.getFilteredUsersStats({include_bots: false, include_deleted: false});
}
onContactUsClick = async (e: any) => {
if (this.state.saving) {
return;
}
this.setState({saving: true, serverError: null});
let forceAck = false;
if (e && e.target && e.target.dataset && e.target.dataset.forceack) {
forceAck = true;
trackEvent('admin', 'click_warn_metric_mailto', {metric: this.props.warnMetricStatus.id});
} else {
trackEvent('admin', 'click_warn_metric_contact_us', {metric: this.props.warnMetricStatus.id});
}
const {error} = await this.props.actions.sendWarnMetricAck(this.props.warnMetricStatus.id, forceAck);
if (error) {
this.setState({serverError: error, saving: false});
} else {
this.onHide();
}
};
onHide = () => {
this.setState({serverError: null, saving: false});
this.setState({gettingTrialError: null, gettingTrial: false});
this.props.actions.closeModal(ModalIdentifiers.WARN_METRIC_ACK);
if (this.props.closeParentComponent) {
this.props.closeParentComponent();
}
};
renderContactUsError = () => {
const {serverError} = this.state;
if (!serverError) {
return '';
}
const mailRecipient = 'support-advisor@mattermost.com';
const mailSubject = 'Mattermost Contact Us request';
let mailBody = 'Mattermost Contact Us request.';
if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
mailBody = 'Mattermost Contact Us request.\r\nMy team now has 500 users, and I am considering Mattermost Enterprise Edition.';
} else if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
mailBody = 'Mattermost Contact Us request.\r\nI am interested in learning more about improving performance with Elasticsearch.';
}
mailBody += '\r\n';
mailBody += 'Contact ' + this.props.user.first_name + ' ' + this.props.user.last_name;
mailBody += '\r\n';
mailBody += 'Email ' + this.props.user.email;
mailBody += '\r\n';
if (this.props.totalUsers) {
mailBody += 'Registered Users ' + this.props.totalUsers;
mailBody += '\r\n';
}
mailBody += 'Site URL ' + getSiteURL();
mailBody += '\r\n';
mailBody += 'Telemetry Id ' + this.props.telemetryId;
mailBody += '\r\n';
mailBody += 'If you have any additional inquiries, please contact support@mattermost.com';
const mailToLinkText = 'mailto:' + mailRecipient + '?cc=' + this.props.user.email + '&subject=' + encodeURIComponent(mailSubject) + '&body=' + encodeURIComponent(mailBody);
return (
<div className='form-group has-error'>
<br/>
<label className='control-label'>
<FormattedMessage
id='warn_metric_ack_modal.mailto.message'
defaultMessage='Support could not be reached. Please {link}.'
values={{
link: (
<WarnMetricAckErrorLink
url={mailToLinkText}
messageId={t('warn_metric_ack_modal.mailto.link')}
forceAck={true}
defaultMessage='email us'
onClickHandler={this.onContactUsClick}
/>
),
}}
/>
</label>
</div>
);
};
render() {
let headerTitle;
if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
headerTitle = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_users.header.title'
defaultMessage='Scaling with Mattermost'
/>
);
} else if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
headerTitle = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_posts.header.title'
defaultMessage='Improve Performance'
/>
);
}
let descriptionText;
const learnMoreLink = 'https://mattermost.com/pl/default-admin-advisory';
if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
descriptionText = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_active_users.description'
defaultMessage='Mattermost strongly recommends that deployments of over {limit}} users take advantage of features such as user management, server clustering, and performance monitoring. Contact us to learn more and let us know how we can help.'
values={{
limit: this.props.warnMetricStatus.limit,
}}
/>
);
} else if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
descriptionText = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_posts.description'
defaultMessage='Your Mattermost system has a large number of messages. The default Mattermost database search starts to show performance degradation at around 2.5 million posts. With over 5 million posts, Elasticsearch can help avoid significant performance issues, such as timeouts, with search and at-mentions. Contact us to learn more and let us know how we can help.'
values={{
limit: this.props.warnMetricStatus.limit,
}}
/>
);
}
const subText = (
<div
style={containerStyles}
className='help__format-text'
>
<FormattedMessage
id='warn_metric_ack_modal.subtext'
defaultMessage='By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}'
values={{
link: (
<ErrorLink
url={learnMoreLink}
messageId={t('warn_metric_ack_modal.learn_more.link')}
defaultMessage='Learn more'
/>
),
}}
/>
</div>
);
const error = this.renderContactUsError();
const footer = (
<Modal.Footer>
<button
className='btn btn-primary save-button'
data-dismiss='modal'
disabled={this.state.saving}
autoFocus={true}
onClick={this.onContactUsClick}
>
<LoadingWrapper
loading={this.state.saving}
text={Utils.localizeMessage('admin.warn_metric.sending-email', 'Sending email')}
>
<FormattedMessage
id='warn_metric_ack_modal.contact_support'
defaultMessage='Acknowledge'
/>
</LoadingWrapper>
</button>
</Modal.Footer>
);
return (
<Modal
dialogClassName='a11y__modal'
show={this.props.show}
keyboard={false}
onHide={this.onHide}
onExited={this.onHide}
role='dialog'
aria-labelledby='warnMetricAckHeaderModalLabel'
>
<Modal.Header closeButton={true}>
<Modal.Title
componentClass='h1'
id='warnMetricAckHeaderModalLabel'
>
{headerTitle}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
{descriptionText}
<br/>
{error}
<br/>
{subText}
</div>
</Modal.Body>
{footer}
</Modal>
);
}
}
type ErrorLinkProps = {
defaultMessage: string;
messageId: string;
onClickHandler: (e: React.MouseEvent) => Promise<void>;
url: string;
forceAck: boolean;
};
const WarnMetricAckErrorLink: React.FC<ErrorLinkProps> = ({defaultMessage, messageId, onClickHandler, url, forceAck}: ErrorLinkProps) => {
return (
<ExternalLink
href={url}
data-forceAck={forceAck}
onClick={onClickHandler}
location='warn_metric_ack_modal'
>
<FormattedMessage
id={messageId}
defaultMessage={defaultMessage}
/>
</ExternalLink>
);
};

View File

@@ -2746,7 +2746,6 @@
"admin.userManagement.userDetail.username": "Username",
"admin.viewArchivedChannelsHelpText": "When true, allows users to view, share and search for content of channels that have been archived. Users can only view the content in channels of which they were a member before the channel was archived.",
"admin.viewArchivedChannelsTitle": "Allow users to view archived channels:",
"admin.warn_metric.sending-email": "Sending email",
"admin.webserverModeDisabled": "Disabled",
"admin.webserverModeDisabledDescription": "The Mattermost server will not serve static files.",
"admin.webserverModeGzip": "gzip",
@@ -2826,15 +2825,7 @@
"announcement_bar.error.trial_license_expiring": "There are {days} days left on your free trial.",
"announcement_bar.error.trial_license_expiring_last_day": "This is the last day of your free trial. Purchase a license now to continue using Mattermost Professional and Enterprise features.",
"announcement_bar.error.trial_license_expiring_last_day.short": "This is the last day of your free trial.",
"announcement_bar.error.warn_metric_status.link": "Learn more",
"announcement_bar.notification.email_verified": "Email verified",
"announcement_bar.number_active_users_warn_metric_status.text": "You now have over {limit} users. We strongly recommend using advanced features for large-scale servers.",
"announcement_bar.number_of_posts_warn_metric_status.text": "You now have over {limit} posts. We strongly advise using advanced features to avoid degraded performance.",
"announcement_bar.warn_metric_status_ack.text": "Thank you for contacting Mattermost. We will follow up with you soon.",
"announcement_bar.warn_metric_status.number_of_posts_ack.text": "Thank you for contacting Mattermost. We will follow up with you soon.",
"announcement_bar.warn_metric_status.number_of_posts.text": "You now have over 2,000,000 posts. We strongly advise using advanced features to avoid degraded performance.",
"announcement_bar.warn_metric_status.number_of_users_ack.text": "Thank you for contacting Mattermost. We will follow up with you soon.",
"announcement_bar.warn_metric_status.number_of_users.text": "You now have over 500 users. We strongly recommend using advanced features for large-scale servers.",
"announcement_bar.warn.contact_support_text": "To renew your license, contact support at support@mattermost.com.",
"announcement_bar.warn.email_support": "[Contact support](!{email}).",
"announcement_bar.warn.no_internet_connection": "Looks like you do not have access to the internet.",
@@ -5849,15 +5840,6 @@
"view_image.zoom_reset": "Reset Zoom",
"view_user_group_modal.ldapSynced": "AD/LDAP SYNCED",
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Member} other {Members}}",
"warn_metric_ack_modal.contact_support": "Acknowledge",
"warn_metric_ack_modal.learn_more.link": "Learn more",
"warn_metric_ack_modal.mailto.link": "email us",
"warn_metric_ack_modal.mailto.message": "Support could not be reached. Please {link}.",
"warn_metric_ack_modal.number_of_active_users.description": "Mattermost strongly recommends that deployments of over {limit} users take advantage of features such as user management, server clustering and performance monitoring. Contact us to learn more and let us know how we can help.",
"warn_metric_ack_modal.number_of_posts.description": "Your Mattermost system has a large number of messages. The default Mattermost database search starts to show performance degradation at around 2.5 million posts. With over 5 million posts, Elasticsearch can help avoid significant performance issues, such as timeouts, with search and at-mentions. Contact us to learn more and let us know how we can help.",
"warn_metric_ack_modal.number_of_posts.header.title": "Improve Performance",
"warn_metric_ack_modal.number_of_users.header.title": "Scaling with Mattermost",
"warn_metric_ack_modal.subtext": "By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}",
"web.footer.about": "About",
"web.footer.help": "Help",
"web.footer.privacy": "Privacy Policy",

View File

@@ -24,9 +24,6 @@ export default keyMirror({
SET_CONFIG_AND_LICENSE: null,
WARN_METRIC_STATUS_RECEIVED: null,
WARN_METRIC_STATUS_REMOVED: null,
FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: null,
FIRST_ADMIN_COMPLETE_SETUP_RECEIVED: null,
SHOW_LAUNCHING_WORKSPACE: null,

View File

@@ -1033,19 +1033,6 @@ describe('Actions.Admin', () => {
expect(nock.isDone()).toBe(true);
});
it('sendWarnMetricAck', async () => {
const warnMetricAck = {
id: 'metric1',
};
nock(Client4.getBaseRoute()).
post('/warn_metrics/ack/metric1').
reply(200, OK_RESPONSE);
await store.dispatch(Actions.sendWarnMetricAck(warnMetricAck.id, false));
expect(nock.isDone()).toBe(true);
});
it('getDataRetentionCustomPolicies', async () => {
const policies = {
policies: [

View File

@@ -560,19 +560,6 @@ export function setSamlIdpCertificateFromMetadata(certData: string) {
});
}
export function sendWarnMetricAck(warnMetricId: string, forceAck: boolean): ActionFuncAsync {
return async (dispatch) => {
try {
Client4.trackEvent('api', 'api_request_send_metric_ack', {warnMetricId});
await Client4.sendWarnMetricAck(warnMetricId, forceAck);
return {data: true};
} catch (e) {
dispatch(logError(e as ServerError));
return {error: (e as ServerError).message};
}
};
}
export function getDataRetentionCustomPolicies(page = 0, perPage = 10): ActionFuncAsync<GetDataRetentionCustomPoliciesRequest> {
return async (dispatch, getState) => {
let data;

View File

@@ -31,7 +31,6 @@ export const PostTypes = {
COMBINED_USER_ACTIVITY: 'system_combined_user_activity' as PostType,
ME: 'me' as PostType,
ADD_BOT_TEAMS_CHANNELS: 'add_bot_teams_channels' as PostType,
SYSTEM_WARN_METRIC_STATUS: 'warn_metric_status' as PostType,
REMINDER: 'reminder' as PostType,
WRANGLER: 'system_wrangler' as PostType,
GM_CONVERTED_TO_CHANNEL: 'system_gm_to_channel' as PostType,

View File

@@ -47,8 +47,6 @@ const WebsocketEvents = {
RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: 'group_not_associated_to_team',
RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: 'group_associated_to_channel',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: 'group_not_associated_to_channel',
WARN_METRIC_STATUS_RECEIVED: 'warn_metric_status_received',
WARN_METRIC_STATUS_REMOVED: 'warn_metric_status_removed',
THREAD_UPDATED: 'thread_updated',
THREAD_FOLLOW_CHANGED: 'thread_follow_changed',
THREAD_READ_CHANGED: 'thread_read_changed',

View File

@@ -48,25 +48,6 @@ function serverVersion(state = '', action: AnyAction) {
}
}
function warnMetricsStatus(state: any = {}, action: AnyAction) {
switch (action.type) {
case GeneralTypes.WARN_METRIC_STATUS_RECEIVED: {
const nextState = {...state};
nextState[action.data.id] = action.data;
return nextState;
}
case GeneralTypes.WARN_METRIC_STATUS_REMOVED: {
const nextState = {...state};
const newParams = Object.assign({}, nextState[action.data.id]);
newParams.acked = true;
nextState[action.data.id] = newParams;
return nextState;
}
default:
return state;
}
}
function firstAdminVisitMarketplaceStatus(state = false, action: AnyAction) {
switch (action.type) {
case GeneralTypes.FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED:
@@ -91,7 +72,6 @@ export default combineReducers({
config,
license,
serverVersion,
warnMetricsStatus,
firstAdminVisitMarketplaceStatus,
firstAdminCompleteSetup,
});

View File

@@ -31,10 +31,6 @@ export const isCloudLicense: (state: GlobalState) => boolean = createSelector(
(license: ClientLicense) => license?.Cloud === 'true',
);
export function warnMetricsStatus(state: GlobalState): any {
return state.entities.general.warnMetricsStatus;
}
export function isCompatibleWithJoinViewTeamPermissions(state: GlobalState): boolean {
const version = state.entities.general.serverVersion;
return isMinimumServerVersion(version, 5, 10, 0) ||

View File

@@ -12,7 +12,6 @@ const state: GlobalState = {
config: {},
license: {},
serverVersion: '',
warnMetricsStatus: {},
firstAdminVisitMarketplaceStatus: false,
firstAdminCompleteSetup: false,
},

View File

@@ -330,18 +330,6 @@ export const PostRequestTypes = keyMirror({
AFTER_ID: null,
});
export const WarnMetricTypes = {
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_100: 'warn_metric_number_of_active_users_100',
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200: 'warn_metric_number_of_active_users_200',
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_300: 'warn_metric_number_of_active_users_300',
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500: 'warn_metric_number_of_active_users_500',
SYSTEM_WARN_METRIC_NUMBER_OF_TEAMS_5: 'warn_metric_number_of_teams_5',
SYSTEM_WARN_METRIC_NUMBER_OF_CHANNELS_5: 'warn_metric_number_of_channels_50',
SYSTEM_WARN_METRIC_MFA: 'warn_metric_mfa',
SYSTEM_WARN_METRIC_EMAIL_DOMAIN: 'warn_metric_email_domain',
SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M: 'warn_metric_number_of_posts_2M',
};
export const ModalIdentifiers = {
ABOUT: 'about',
TEAM_SETTINGS: 'team_settings',
@@ -381,7 +369,6 @@ export const ModalIdentifiers = {
EDIT_CATEGORY: 'edit_category',
DELETE_CATEGORY: 'delete_category',
SIDEBAR_WHATS_NEW_MODAL: 'sidebar_whats_new_modal',
WARN_METRIC_ACK: 'warn_metric_acknowledgement',
UPGRADE_CLOUD_ACCOUNT: 'upgrade_cloud_account',
START_TRIAL_MODAL: 'start_trial_modal',
TRIAL_BENEFITS_MODAL: 'trial_benefits_modal',
@@ -638,8 +625,6 @@ export const SocketEvents = {
RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: 'received_group_not_associated_to_team',
RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: 'received_group_associated_to_channel',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: 'received_group_not_associated_to_channel',
WARN_METRIC_STATUS_RECEIVED: 'warn_metric_status_received',
WARN_METRIC_STATUS_REMOVED: 'warn_metric_status_removed',
SIDEBAR_CATEGORY_CREATED: 'sidebar_category_created',
SIDEBAR_CATEGORY_UPDATED: 'sidebar_category_updated',
SIDEBAR_CATEGORY_DELETED: 'sidebar_category_deleted',
@@ -933,10 +918,6 @@ export const AnnouncementBarMessages = {
LICENSE_PAST_GRACE: t('announcement_bar.error.past_grace'),
PREVIEW_MODE: t('announcement_bar.error.preview_mode'),
WEBSOCKET_PORT_ERROR: t('channel_loader.socketError'),
WARN_METRIC_STATUS_NUMBER_OF_USERS: t('announcement_bar.warn_metric_status.number_of_users.text'),
WARN_METRIC_STATUS_NUMBER_OF_USERS_ACK: t('announcement_bar.warn_metric_status.number_of_users_ack.text'),
WARN_METRIC_STATUS_NUMBER_OF_POSTS: t('announcement_bar.warn_metric_status.number_of_posts.text'),
WARN_METRIC_STATUS_NUMBER_OF_POSTS_ACK: t('announcement_bar.warn_metric_status.number_of_posts_ack.text'),
TRIAL_LICENSE_EXPIRING: t('announcement_bar.error.trial_license_expiring'),
};

View File

@@ -2517,20 +2517,6 @@ export default class Client4 {
);
};
getWarnMetricsStatus = async () => {
return this.doFetch(
`${this.getBaseRoute()}/warn_metrics/status`,
{method: 'get'},
);
};
sendWarnMetricAck = async (warnMetricId: string, forceAckVal: boolean) => {
return this.doFetch(
`${this.getBaseRoute()}/warn_metrics/ack/${encodeURI(warnMetricId)}`,
{method: 'post', body: JSON.stringify({forceAck: forceAckVal})},
);
}
setFirstAdminVisitMarketplaceStatus = async () => {
return this.doFetch<StatusOK>(
`${this.getPluginsRoute()}/marketplace/first_admin_visit`,

View File

@@ -9,7 +9,6 @@ export type GeneralState = {
firstAdminCompleteSetup: boolean;
license: ClientLicense;
serverVersion: string;
warnMetricsStatus: Record<string, WarnMetricStatus>;
};
export type SystemSetting = {