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
Этот коммит содержится в:
Maria A Nunez
2025-05-19 14:31:47 -04:00
коммит произвёл GitHub
родитель 2116a6d94a
Коммит 2213cdbaf7
13 изменённых файлов: 447 добавлений и 66 удалений

Просмотреть файл

@@ -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 = {