User usage alert in License and System Statistics page (#31063)
* Added user over usager alert in License and System Statistics page * Fixed case below 90 * Fixed constant * Linting * Change over user notification threshold to 0 * Fix tests * Saved dismissed preference * Text tweek * Fix tests * More tests fixed * Snapshot updated
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
2116a6d94a
Коммит
2213cdbaf7
@@ -19,6 +19,22 @@ exports[`components/admin_console/license_settings/LicenseSettings load screen a
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -123,6 +139,22 @@ exports[`components/admin_console/license_settings/LicenseSettings load screen w
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -227,6 +259,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E10",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -362,6 +410,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -485,6 +549,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "enterprise",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -608,6 +688,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -731,6 +827,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -854,6 +966,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -977,6 +1105,18 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1617714643650",
|
||||
"StartsAt": "1617714643650",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<TrialLicenseCard
|
||||
license={
|
||||
Object {
|
||||
@@ -1078,6 +1218,15 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"IsLicensed": "false",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<Component
|
||||
enterpriseReady={true}
|
||||
gettingTrial={false}
|
||||
@@ -1190,6 +1339,15 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"IsLicensed": "false",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<Component
|
||||
enterpriseReady={true}
|
||||
gettingTrial={false}
|
||||
@@ -1302,6 +1460,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620335443650",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
@@ -1406,6 +1580,15 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"IsLicensed": "false",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<Component
|
||||
enterpriseReady={false}
|
||||
gettingTrial={false}
|
||||
@@ -1519,7 +1702,17 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
>
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
/>
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"IsLicensed": "false",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="top-wrapper"
|
||||
>
|
||||
@@ -1616,7 +1809,25 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
>
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
/>
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Cloud": "true",
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620993600000",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="top-wrapper"
|
||||
>
|
||||
@@ -1725,6 +1936,22 @@ exports[`components/admin_console/license_settings/LicenseSettings should match
|
||||
<div
|
||||
className="admin-console__banner_section"
|
||||
>
|
||||
<UserSeatAlertBanner
|
||||
license={
|
||||
Object {
|
||||
"Company": "Mattermost Inc.",
|
||||
"ExpiresAt": "1620993600000",
|
||||
"IsLicensed": "true",
|
||||
"IssuedAt": "1517714643650",
|
||||
"Name": "LicenseName",
|
||||
"SkuShortName": "E20",
|
||||
"StartsAt": "1517714643650",
|
||||
"Users": "100",
|
||||
}
|
||||
}
|
||||
location="license_settings"
|
||||
totalUsers={10}
|
||||
/>
|
||||
<RenewLicenseCard
|
||||
isLicenseExpired={false}
|
||||
license={
|
||||
|
||||
@@ -14,7 +14,7 @@ import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {OverActiveUserLimits, SelfHostedProducts} from 'utils/constants';
|
||||
import {SelfHostedProducts} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import EnterpriseEditionLeftPanel from './enterprise_edition_left_panel';
|
||||
@@ -132,30 +132,9 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
||||
expect(screen.getByText('ACTIVE USERS:')).not.toHaveClass('legend--over-seats-purchased');
|
||||
});
|
||||
|
||||
test('should add warning class to active users', () => {
|
||||
const minWarning = Math.ceil(parseInt(license.Users, 10) * OverActiveUserLimits.MIN) + parseInt(license.Users, 10);
|
||||
const props = {
|
||||
...baseProps,
|
||||
statsActiveUsers: minWarning,
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<EnterpriseEditionLeftPanel
|
||||
{...props}
|
||||
/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(screen.getByText(Intl.NumberFormat('en').format(minWarning))).toHaveClass('value');
|
||||
expect(screen.getByText(Intl.NumberFormat('en').format(minWarning))).toHaveClass('value--warning-over-seats-purchased');
|
||||
expect(screen.getByText(Intl.NumberFormat('en').format(minWarning))).not.toHaveClass('value--over-seats-purchased');
|
||||
expect(screen.getByText('ACTIVE USERS:')).toHaveClass('legend');
|
||||
expect(screen.getByText('ACTIVE USERS:')).toHaveClass('legend--warning-over-seats-purchased');
|
||||
expect(screen.getByText('ACTIVE USERS:')).not.toHaveClass('legend--over-seats-purchased');
|
||||
});
|
||||
|
||||
test('should add over-seats-purchased class to active users', () => {
|
||||
const exceedHighLimitExtraUsersError = Math.ceil(parseInt(license.Users, 10) * OverActiveUserLimits.MAX) + parseInt(license.Users, 10);
|
||||
// Changed to not use the constant OverActiveUserLimits.MAX given that we are currently set to 0. So the active users will be 0
|
||||
const exceedHighLimitExtraUsersError = Math.ceil(parseInt(license.Users, 10) * 0.2) + parseInt(license.Users, 10);
|
||||
const props = {
|
||||
...baseProps,
|
||||
statsActiveUsers: exceedHighLimitExtraUsersError,
|
||||
|
||||
@@ -34,6 +34,7 @@ import TeamEditionLeftPanel from './team_edition/team_edition_left_panel';
|
||||
import TeamEditionRightPanel from './team_edition/team_edition_right_panel';
|
||||
import TrialBanner from './trial_banner/trial_banner';
|
||||
import TrialLicenseCard from './trial_license_card/trial_license_card';
|
||||
import UserSeatAlertBanner from './user_seat_alert_banner';
|
||||
|
||||
import './license_settings.scss';
|
||||
|
||||
@@ -354,6 +355,11 @@ export default class LicenseSettings extends React.PureComponent<Props, State> {
|
||||
<div className='admin-console__wrapper'>
|
||||
<div className='admin-console__content'>
|
||||
<div className='admin-console__banner_section'>
|
||||
<UserSeatAlertBanner
|
||||
license={license}
|
||||
totalUsers={this.props.totalUsers}
|
||||
location='license_settings'
|
||||
/>
|
||||
{!this.state.clickNormalUpgradeBtn && license.IsLicensed !== 'true' &&
|
||||
this.props.prevTrialLicense?.IsLicensed !== 'true' &&
|
||||
<TrialBanner
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import type {ClientLicense} from '@mattermost/types/config';
|
||||
|
||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {get as selectPreference} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import ExternalLink from 'components/external_link';
|
||||
|
||||
import {Preferences, LicenseLinks} from 'utils/constants';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import AlertBanner from '../../alert_banner';
|
||||
|
||||
export interface UserSeatAlertBannerProps {
|
||||
license: ClientLicense;
|
||||
totalUsers: number;
|
||||
location: 'license_settings' | 'system_statistics';
|
||||
}
|
||||
|
||||
const UserSeatAlertBanner: React.FC<UserSeatAlertBannerProps> = ({license, totalUsers, location}) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
|
||||
const getPreferenceName = (percentUsed: number) => {
|
||||
if (percentUsed < 90) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const locationSuffix = location === 'license_settings' ? 'license_settings' : 'system_statistics';
|
||||
if (percentUsed >= 100) {
|
||||
return `100_seat_${locationSuffix}`;
|
||||
} else if (percentUsed >= 95) {
|
||||
return `95_seat_${locationSuffix}`;
|
||||
}
|
||||
return `90_seat_${locationSuffix}`;
|
||||
};
|
||||
|
||||
const calculatePercentUsed = () => {
|
||||
if (!license || !license.Users) {
|
||||
return 0;
|
||||
}
|
||||
const licensedUsers = parseInt(license.Users, 10);
|
||||
|
||||
if (!licensedUsers || licensedUsers === 0) {
|
||||
return 0;
|
||||
}
|
||||
return (totalUsers / licensedUsers) * 100;
|
||||
};
|
||||
|
||||
const percentUsed = calculatePercentUsed();
|
||||
const preferenceName = getPreferenceName(percentUsed);
|
||||
const dismissed = useSelector((state: GlobalState) => selectPreference(state, Preferences.CATEGORY_SYSTEM_NOTICE, preferenceName, 'false'));
|
||||
const [visible, setVisible] = useState(dismissed === 'false');
|
||||
|
||||
useEffect(() => {
|
||||
setVisible(dismissed === 'false');
|
||||
}, [dismissed]);
|
||||
|
||||
const handleDismiss = () => {
|
||||
setVisible(false);
|
||||
|
||||
dispatch(savePreferences(currentUser.id, [{
|
||||
user_id: currentUser.id,
|
||||
category: Preferences.CATEGORY_SYSTEM_NOTICE,
|
||||
name: preferenceName,
|
||||
value: 'true',
|
||||
}]));
|
||||
};
|
||||
|
||||
if (!visible || !license || !license.Users || percentUsed < 90) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let mode: 'success' | 'info' | 'danger' = 'info';
|
||||
let title: React.ReactNode = null;
|
||||
let message: React.ReactNode = null;
|
||||
|
||||
if (percentUsed >= 90 && percentUsed < 95) {
|
||||
mode = 'success';
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.successTitle'
|
||||
defaultMessage='Your workspace has reached 90% of your licensed seats'
|
||||
/>
|
||||
);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.successMessage'
|
||||
defaultMessage='Congratulations! Platform adoption is strong across your organization. To ensure uninterrupted growth, our team can assist in scaling your license to meet operational requirements.'
|
||||
/>
|
||||
);
|
||||
} else if (percentUsed >= 95 && percentUsed < 100) {
|
||||
mode = 'info';
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.infoTitle'
|
||||
defaultMessage='Your workspace has reached 95% of your licensed seats'
|
||||
/>
|
||||
);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.infoMessage'
|
||||
defaultMessage='Your organization is approaching full license utilization. Now is a good time to assess future needs and align usage with procurement planning. Contact us to explore available options.'
|
||||
/>
|
||||
);
|
||||
} else if (percentUsed >= 100) {
|
||||
mode = 'danger';
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.dangerTitle'
|
||||
defaultMessage='Your workspace has reached 100% of your licensed seats'
|
||||
/>
|
||||
);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.dangerMessage'
|
||||
defaultMessage='All licensed seats are now in use. Additional users may result in true-up charges at your next renewal. To maintain compliance and uninterrupted access, you may limit new sign-ups, or contact us to extend your license.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const actionButtonLeft = (
|
||||
<ExternalLink
|
||||
href={LicenseLinks.CONTACT_SALES}
|
||||
location='license_settings_user_seat_alert'
|
||||
className='style-button AlertBanner__buttonLeft'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.license.userSeatAlert.contactSales'
|
||||
defaultMessage='Contact Sales'
|
||||
/>
|
||||
</ExternalLink>
|
||||
);
|
||||
|
||||
return (
|
||||
<AlertBanner
|
||||
mode={mode}
|
||||
title={title}
|
||||
message={message}
|
||||
actionButtonLeft={actionButtonLeft}
|
||||
onDismiss={handleDismiss}
|
||||
closeBtnTooltip={formatMessage({id: 'admin.license.userSeatAlert.closeBtnTooltip', defaultMessage: 'Dismiss'})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSeatAlertBanner;
|
||||
@@ -49,7 +49,8 @@
|
||||
}
|
||||
|
||||
.AlertBanner__actionButtons {
|
||||
margin-top: 12px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 16px;
|
||||
font-family: 'Open Sans';
|
||||
|
||||
.AlertBanner__buttonLeft {
|
||||
|
||||
@@ -53,7 +53,7 @@ const ActivatedUserCard = ({activatedUsers, seatsPurchased, isCloud}: ActivatedU
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='analytics.team.overageUsersSeats'
|
||||
defaultMessage='This exceeds total paid seats'
|
||||
defaultMessage='This exceeds total licensed seats'
|
||||
>
|
||||
{(text) => <span>{text}</span>}
|
||||
</FormattedMessage>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import * as AdminActions from 'actions/admin_actions.jsx';
|
||||
|
||||
import UserSeatAlertBanner from 'components/admin_console/license_settings/user_seat_alert_banner';
|
||||
import ActivatedUserCard from 'components/analytics/activated_users_card';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||
@@ -510,6 +511,11 @@ export default class SystemAnalytics extends React.PureComponent<Props, State> {
|
||||
</AdminHeader>
|
||||
<div className='admin-console__wrapper'>
|
||||
<div className='admin-console__content'>
|
||||
<UserSeatAlertBanner
|
||||
license={this.props.license}
|
||||
totalUsers={this.getStatValue(stats[StatTypes.TOTAL_USERS]) || 0}
|
||||
location='system_statistics'
|
||||
/>
|
||||
{banner}
|
||||
<div className='grid-statistics'>
|
||||
{systemCards}
|
||||
|
||||
@@ -27,15 +27,10 @@ import './overage_users_banner.scss';
|
||||
type AdminHasDismissedItArgs = {
|
||||
preferenceName: string;
|
||||
overagePreferences: PreferenceType[];
|
||||
isWarningBanner: boolean;
|
||||
}
|
||||
|
||||
const adminHasDismissed = ({preferenceName, overagePreferences, isWarningBanner}: AdminHasDismissedItArgs): boolean => {
|
||||
if (isWarningBanner) {
|
||||
return overagePreferences.find((value) => value.name === preferenceName) !== undefined;
|
||||
}
|
||||
|
||||
return false;
|
||||
const adminHasDismissed = ({preferenceName, overagePreferences}: AdminHasDismissedItArgs): boolean => {
|
||||
return overagePreferences.find((value) => value.name === preferenceName) !== undefined;
|
||||
};
|
||||
|
||||
const OverageUsersBanner = () => {
|
||||
@@ -62,7 +57,7 @@ const OverageUsersBanner = () => {
|
||||
|
||||
const overageByUsers = activeUsers - seatsPurchased;
|
||||
|
||||
const isOverageState = isBetween5PercerntAnd10PercentPurchasedSeats || isOver10PercerntPurchasedSeats;
|
||||
const isOverageState = overageByUsers > 0 && (isBetween5PercerntAnd10PercentPurchasedSeats || isOver10PercerntPurchasedSeats);
|
||||
const hasPermission = isAdmin && isOverageState && !isCloud;
|
||||
const {
|
||||
cta,
|
||||
@@ -89,14 +84,14 @@ const OverageUsersBanner = () => {
|
||||
|
||||
const handleClick = handleContactSalesClick;
|
||||
|
||||
if (!hasPermission || adminHasDismissed({isWarningBanner: isBetween5PercerntAnd10PercentPurchasedSeats, overagePreferences, preferenceName})) {
|
||||
if (!hasPermission || adminHasDismissed({overagePreferences, preferenceName})) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id='licensingPage.overageUsersBanner.text'
|
||||
defaultMessage='(Only visible to admins) Your workspace user count has exceeded your paid license seat count by {seats, number} {seats, plural, one {seat} other {seats}}. Purchase additional seats to remain compliant.'
|
||||
defaultMessage='(Only visible to admins) The user count exceeds the number of licensed seats by {seats, number} {seats, plural, one {seat} other {seats}}. Purchase more seats to stay compliant.'
|
||||
values={{
|
||||
seats: overageByUsers,
|
||||
}}
|
||||
@@ -105,7 +100,7 @@ const OverageUsersBanner = () => {
|
||||
return (
|
||||
<AnnouncementBar
|
||||
type={isBetween5PercerntAnd10PercentPurchasedSeats ? AnnouncementBarTypes.ADVISOR : AnnouncementBarTypes.CRITICAL}
|
||||
showCloseButton={isBetween5PercerntAnd10PercentPurchasedSeats}
|
||||
showCloseButton={true}
|
||||
onButtonClick={handleClick}
|
||||
modalButtonText={cta}
|
||||
message={message}
|
||||
|
||||
@@ -39,12 +39,12 @@ jest.mock('actions/telemetry_actions', () => ({
|
||||
const seatsPurchased = 40;
|
||||
const email = 'test@mattermost.com';
|
||||
|
||||
const seatsMinimumFor5PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MIN)) + seatsPurchased;
|
||||
const seatsMinimumFor5PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MIN)) + seatsPurchased + 1;
|
||||
|
||||
const seatsMinimumFor10PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MAX)) + seatsPurchased;
|
||||
const seatsMinimumFor10PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MAX)) + seatsPurchased + 1;
|
||||
|
||||
const text5PercentageState = `(Only visible to admins) Your workspace user count has exceeded your paid license seat count by ${seatsMinimumFor5PercentageState - seatsPurchased} seats. Purchase additional seats to remain compliant.`;
|
||||
const text10PercentageState = `(Only visible to admins) Your workspace user count has exceeded your paid license seat count by ${seatsMinimumFor10PercentageState - seatsPurchased} seats. Purchase additional seats to remain compliant.`;
|
||||
const text5PercentageState = `(Only visible to admins) The user count exceeds the number of licensed seats by ${seatsMinimumFor5PercentageState - seatsPurchased} seat. Purchase more seats to stay compliant.`;
|
||||
const text10PercentageState = `(Only visible to admins) The user count exceeds the number of licensed seats by ${seatsMinimumFor10PercentageState - seatsPurchased} seat. Purchase more seats to stay compliant.`;
|
||||
|
||||
const contactSalesTextLink = 'Contact Sales';
|
||||
|
||||
@@ -127,7 +127,7 @@ describe('components/overage_users_banner', () => {
|
||||
it('should not render the banner because we are not on overage state', () => {
|
||||
renderWithContext(<OverageUsersBanner/>);
|
||||
|
||||
expect(screen.queryByText('(Only visible to admins) Your workspace user count has exceeded your paid license seat count by', {exact: false})).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('(Only visible to admins) The user count exceeds the number of licensed seats by', {exact: false})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the banner because we are not admins', () => {
|
||||
@@ -146,7 +146,7 @@ describe('components/overage_users_banner', () => {
|
||||
|
||||
renderWithContext(<OverageUsersBanner/>, store);
|
||||
|
||||
expect(screen.queryByText('Your workspace user count has exceeded your paid license seat count by', {exact: false})).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Your workspace user count has exceeded your licensed seat count by', {exact: false})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the banner because it\'s cloud licenese', () => {
|
||||
@@ -159,7 +159,7 @@ describe('components/overage_users_banner', () => {
|
||||
|
||||
renderWithContext(<OverageUsersBanner/>, store);
|
||||
|
||||
expect(screen.queryByText('Your workspace user count has exceeded your paid license seat count by', {exact: false})).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Your workspace user count has exceeded your licensed seat count by', {exact: false})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the 5% banner because we have dissmised it', () => {
|
||||
@@ -170,7 +170,7 @@ describe('components/overage_users_banner', () => {
|
||||
{
|
||||
category: Preferences.OVERAGE_USERS_BANNER,
|
||||
value: 'Overage users banner watched',
|
||||
name: `warn_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
name: `error_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -230,7 +230,7 @@ describe('components/overage_users_banner', () => {
|
||||
const salesLinkWithEncodedParams = 'https://mattermost.com/contact-sales/?qk=&qp=&qw=&qx=dGVzdEBtYXR0ZXJtb3N0LmNvbQ==&utm_source=mattermost&utm_medium=in-product';
|
||||
expect(windowSpy).toBeCalledWith(salesLinkWithEncodedParams, '_blank');
|
||||
expect(trackEvent).toBeCalledTimes(1);
|
||||
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_warning', {
|
||||
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_error', {
|
||||
cta: 'Contact Sales',
|
||||
banner: 'global banner',
|
||||
});
|
||||
@@ -248,7 +248,7 @@ describe('components/overage_users_banner', () => {
|
||||
{
|
||||
category: Preferences.OVERAGE_USERS_BANNER,
|
||||
value: 'Overage users banner watched',
|
||||
name: `warn_overage_seats_${10}`,
|
||||
name: `error_overage_seats_${10}`,
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -283,7 +283,7 @@ describe('components/overage_users_banner', () => {
|
||||
expect(savePreferences).toBeCalledTimes(1);
|
||||
expect(savePreferences).toBeCalledWith(store.entities.users.profiles.current_user.id, [{
|
||||
category: Preferences.OVERAGE_USERS_BANNER,
|
||||
name: `warn_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
name: `error_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
user_id: store.entities.users.profiles.current_user.id,
|
||||
value: 'Overage users banner watched',
|
||||
}]);
|
||||
@@ -319,7 +319,7 @@ describe('components/overage_users_banner', () => {
|
||||
store.entities.admin = {
|
||||
...store.entities.admin,
|
||||
analytics: {
|
||||
[StatTypes.TOTAL_USERS]: seatsMinimumFor10PercentageState,
|
||||
[StatTypes.TOTAL_USERS]: seatsMinimumFor10PercentageState + 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ const OverageUsersBannerNotice = () => {
|
||||
const preferenceName = `${prefixPreferences}_overage_seats_${prefixLicenseId}`;
|
||||
|
||||
const overageByUsers = activeUsers - seatsPurchased;
|
||||
const isOverageState = isBetween5PercerntAnd10PercentPurchasedSeats || isOver10PercerntPurchasedSeats;
|
||||
const isOverageState = overageByUsers > 0 && (isBetween5PercerntAnd10PercentPurchasedSeats || isOver10PercerntPurchasedSeats);
|
||||
const hasPermission = isAdmin && isOverageState && !isCloud;
|
||||
const {
|
||||
cta,
|
||||
@@ -120,7 +120,7 @@ const OverageUsersBannerNotice = () => {
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='licensingPage.overageUsersBanner.noticeTitle'
|
||||
defaultMessage='Your workspace user count has exceeded your paid license seat count by {seats, number} {seats, plural, one {seat} other {seats}}'
|
||||
defaultMessage='Your workspace user count has exceeded your licensed seat count by {seats, number} {seats, plural, one {seat} other {seats}}'
|
||||
values={{
|
||||
seats: overageByUsers,
|
||||
}}
|
||||
|
||||
@@ -41,12 +41,12 @@ jest.mock('actions/telemetry_actions', () => ({
|
||||
|
||||
const seatsPurchased = 40;
|
||||
|
||||
const seatsMinimumFor5PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MIN)) + seatsPurchased;
|
||||
const seatsMinimumFor5PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MIN)) + seatsPurchased + 1;
|
||||
|
||||
const seatsMinimumFor10PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MAX)) + seatsPurchased;
|
||||
const seatsMinimumFor10PercentageState = (Math.ceil(seatsPurchased * OverActiveUserLimits.MAX)) + seatsPurchased + 1;
|
||||
|
||||
const text5PercentageState = `Your workspace user count has exceeded your paid license seat count by ${seatsMinimumFor5PercentageState - seatsPurchased} seats`;
|
||||
const text10PercentageState = `Your workspace user count has exceeded your paid license seat count by ${seatsMinimumFor10PercentageState - seatsPurchased} seats`;
|
||||
const text5PercentageState = `Your workspace user count has exceeded your licensed seat count by ${seatsMinimumFor5PercentageState - seatsPurchased} seat`;
|
||||
const text10PercentageState = `Your workspace user count has exceeded your licensed seat count by ${seatsMinimumFor10PercentageState - seatsPurchased} seat`;
|
||||
const notifyText = 'Notify your Customer Success Manager on your next true-up check';
|
||||
|
||||
const contactSalesTextLink = 'Contact Sales';
|
||||
@@ -169,7 +169,7 @@ describe('components/invitation_modal/overage_users_banner_notice', () => {
|
||||
{
|
||||
category: Preferences.OVERAGE_USERS_BANNER,
|
||||
value: 'Overage users banner watched',
|
||||
name: `warn_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
name: `error_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -234,7 +234,7 @@ describe('components/invitation_modal/overage_users_banner_notice', () => {
|
||||
'?utm_source=mattermost&utm_medium=in-product&utm_content=overage_users_banner&uid=current_user&sid=',
|
||||
);
|
||||
expect(trackEvent).toBeCalledTimes(2);
|
||||
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_warning', {
|
||||
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_error', {
|
||||
cta: 'Contact Sales',
|
||||
banner: 'invite modal',
|
||||
});
|
||||
@@ -248,7 +248,7 @@ describe('components/invitation_modal/overage_users_banner_notice', () => {
|
||||
{
|
||||
category: Preferences.OVERAGE_USERS_BANNER,
|
||||
value: 'Overage users banner watched',
|
||||
name: `warn_overage_seats_${generateId().substring(0, 8)}`,
|
||||
name: `error_overage_seats_${generateId().substring(0, 8)}`,
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -289,7 +289,7 @@ describe('components/invitation_modal/overage_users_banner_notice', () => {
|
||||
expect(savePreferences).toBeCalledTimes(1);
|
||||
expect(savePreferences).toBeCalledWith(store.entities.users.profiles.current_user.id, [{
|
||||
category: Preferences.OVERAGE_USERS_BANNER,
|
||||
name: `warn_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
name: `error_overage_seats_${licenseId.substring(0, 8)}`,
|
||||
user_id: store.entities.users.profiles.current_user.id,
|
||||
value: 'Overage users banner watched',
|
||||
}]);
|
||||
|
||||
@@ -1565,6 +1565,14 @@
|
||||
"admin.license.upload-modal.successfulUpgradeText": "You have upgraded to the {skuName} plan for {licensedUsersNum, number} seats. This is effective from {startsAt} until {expiresAt}. ",
|
||||
"admin.license.upload-modal.title": "Upload a License Key",
|
||||
"admin.license.uploadFile": "Upload File",
|
||||
"admin.license.userSeatAlert.closeBtnTooltip": "Dismiss",
|
||||
"admin.license.userSeatAlert.contactSales": "Contact Sales",
|
||||
"admin.license.userSeatAlert.dangerMessage": "All licensed seats are now in use. Additional users may result in true-up charges at your next renewal. To maintain compliance and uninterrupted access, you may limit new sign-ups, or contact us to extend your license.",
|
||||
"admin.license.userSeatAlert.dangerTitle": "Your workspace has reached 100% of your licensed seats",
|
||||
"admin.license.userSeatAlert.infoMessage": "Your organization is approaching full license utilization. Now is a good time to assess future needs and align usage with procurement planning. Contact us to explore available options.",
|
||||
"admin.license.userSeatAlert.infoTitle": "Your workspace has reached 95% of your licensed seats",
|
||||
"admin.license.userSeatAlert.successMessage": "Congratulations! Platform adoption is strong across your organization. To ensure uninterrupted growth, our team can assist in scaling your license to meet operational requirements.",
|
||||
"admin.license.userSeatAlert.successTitle": "Your workspace has reached 90% of your licensed seats",
|
||||
"admin.licenseSettings.teamEdition.teamEditionRightPanel.acceptTermsFinal": ". Upgrading will download the binary and update your team edition.",
|
||||
"admin.licenseSettings.teamEdition.teamEditionRightPanel.acceptTermsInitial": "By clicking <b>Upgrade</b>, I agree to the terms of the Mattermost ",
|
||||
"admin.licenseSettings.teamEdition.teamEditionRightPanel.eeLicenseLink": "Enterprise Edition License",
|
||||
@@ -3105,7 +3113,7 @@
|
||||
"analytics.team.activeUsers": "Active Users With Posts",
|
||||
"analytics.team.newlyCreated": "Newly Created Users",
|
||||
"analytics.team.noTeams": "This server has no teams for which to view statistics.",
|
||||
"analytics.team.overageUsersSeats": "This exceeds total paid seats",
|
||||
"analytics.team.overageUsersSeats": "This exceeds total licensed seats",
|
||||
"analytics.team.privateGroups": "Private Channels",
|
||||
"analytics.team.publicChannels": "Public Channels",
|
||||
"analytics.team.recentUsers": "Recent Active Users",
|
||||
@@ -4462,8 +4470,8 @@
|
||||
"licensingPage.infoBanner.startTrialTitle": "Start a free 30-day tral of Mattermost Enterprise Advanced",
|
||||
"licensingPage.overageUsersBanner.cta": "Contact Sales",
|
||||
"licensingPage.overageUsersBanner.noticeDescription": "Notify your Customer Success Manager on your next true-up check. <a></a>",
|
||||
"licensingPage.overageUsersBanner.noticeTitle": "Your workspace user count has exceeded your paid license seat count by {seats, number} {seats, plural, one {seat} other {seats}}",
|
||||
"licensingPage.overageUsersBanner.text": "(Only visible to admins) Your workspace user count has exceeded your paid license seat count by {seats, number} {seats, plural, one {seat} other {seats}}. Purchase additional seats to remain compliant.",
|
||||
"licensingPage.overageUsersBanner.noticeTitle": "Your workspace user count has exceeded your licensed seat count by {seats, number} {seats, plural, one {seat} other {seats}}",
|
||||
"licensingPage.overageUsersBanner.text": "(Only visible to admins) The user count exceeds the number of licensed seats by {seats, number} {seats, plural, one {seat} other {seats}}. Purchase more seats to stay compliant.",
|
||||
"link_preview.image_preview": "Show Image preview",
|
||||
"link_preview.remove_link_preview": "Remove link preview",
|
||||
"list_modal.paginatorCount": "{startCount, number} - {endCount, number} of {total, number} total",
|
||||
|
||||
@@ -2250,9 +2250,12 @@ export const DataSearchTypes = {
|
||||
MESSAGES_SEARCH_TYPE: 'messages',
|
||||
} as const;
|
||||
|
||||
// Currently, the overage user limits are set to 0, which means that the overage active users warning
|
||||
// is immediatly shown when over 100% usage.
|
||||
// Originally it was set to Min 5% and Max 10% (5% would be a blue warning and 10% would be a red warning).
|
||||
export const OverActiveUserLimits = {
|
||||
MIN: 0.05,
|
||||
MAX: 0.1,
|
||||
MIN: 0,
|
||||
MAX: 0,
|
||||
} as const;
|
||||
|
||||
export const PageLoadContext = {
|
||||
|
||||
Ссылка в новой задаче
Block a user