Update text for activated users in team statistics (#25921)

* Update text for activated users in team statistics

* Fix lint

* Generalize tooltip and make it show according to the designs

* Fix lint

* Fix test

* Generalize even more overlay trigger

* Update snapshot

* Update link
This commit is contained in:
Daniel Espino García 2024-01-19 10:49:47 +01:00 committed by GitHub
parent 7833dd2c1c
commit f8dbb2e168
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 337 additions and 168 deletions

View File

@ -565,104 +565,26 @@ exports[`admin_console/team_channel_settings/team/TeamProfile__Cloud should matc
<div
className="AdminChannelDetails_archiveContainer"
>
<OverlayTrigger
defaultOverlayShown={false}
delay={400}
disabled={false}
overlay={
<Tooltip
id="sharedTooltip"
>
<div
className="tooltip-title"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Upgrade to Unarchive"
id="workspace_limits.teams_limit_reached.upgrade_to_unarchive"
/>
</div>
<div
className="tooltip-body"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="You've reached the team limit for your current plan. Consider upgrading to unarchive this team or archive your other teams"
id="workspace_limits.teams_limit_reached.tool_tip"
/>
</div>
</Tooltip>
<WithTooltip
hint={
Object {
"defaultMessage": "You've reached the team limit for your current plan. Consider upgrading to unarchive this team or archive your other teams",
"id": "workspace_limits.teams_limit_reached.tool_tip",
}
}
id="sharedTooltip"
placement="bottom"
trigger={
Array [
"hover",
"focus",
]
title={
Object {
"defaultMessage": "Upgrade to Unarchive",
"id": "workspace_limits.teams_limit_reached.upgrade_to_unarchive",
}
}
>
<OverlayTrigger
defaultOverlayShown={false}
delay={400}
overlay={
<OverlayWrapper
id="sharedTooltip"
intl={
Object {
"$t": [Function],
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"fallbackOnEmptyString": true,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
"formatPlural": [Function],
"formatRelativeTime": [Function],
"formatTime": [Function],
"formatTimeToParts": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getDisplayNames": [Function],
"getListFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralRules": [Function],
"getRelativeTimeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"onError": [Function],
"onWarn": [Function],
"textComponent": "span",
"timeZone": "Etc/UTC",
"wrapRichTextChunksInFragment": undefined,
}
}
>
<div
className="tooltip-title"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Upgrade to Unarchive"
id="workspace_limits.teams_limit_reached.upgrade_to_unarchive"
/>
</div>
<div
className="tooltip-body"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="You've reached the team limit for your current plan. Consider upgrading to unarchive this team or archive your other teams"
id="workspace_limits.teams_limit_reached.tool_tip"
/>
</div>
</OverlayWrapper>
}
overlay={<Unknown />}
placement="bottom"
trigger={
Array [
@ -671,40 +593,95 @@ exports[`admin_console/team_channel_settings/team/TeamProfile__Cloud should matc
]
}
>
<div
className="disabled-overlay-wrapper"
onBlur={[Function]}
onClick={null}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<button
className="btn btn-danger ArchiveButton ArchiveButton___archived cloud-limits-disabled"
disabled={true}
onClick={[Function]}
style={
Object {
"pointerEvents": "none",
<OverlayTrigger
defaultOverlayShown={false}
delay={400}
overlay={
<OverlayWrapper
intl={
Object {
"$t": [Function],
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"fallbackOnEmptyString": true,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
"formatPlural": [Function],
"formatRelativeTime": [Function],
"formatTime": [Function],
"formatTimeToParts": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getDisplayNames": [Function],
"getListFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralRules": [Function],
"getRelativeTimeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"onError": [Function],
"onWarn": [Function],
"textComponent": "span",
"timeZone": "Etc/UTC",
"wrapRichTextChunksInFragment": undefined,
}
}
}
type="button"
>
<i
className="icon icon-archive-arrow-up-outline"
/>
<FormattedMessage
defaultMessage="Unarchive Team"
id="admin.team_settings.team_details.unarchiveTeam"
}
placement="bottom"
trigger={
Array [
"hover",
"focus",
]
}
>
<div
className="disabled-overlay-wrapper"
onBlur={[Function]}
onClick={null}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<button
className="btn btn-danger ArchiveButton ArchiveButton___archived cloud-limits-disabled"
disabled={true}
onClick={[Function]}
style={
Object {
"pointerEvents": "none",
}
}
type="button"
>
<span>
Unarchive Team
</span>
</FormattedMessage>
</button>
</div>
<i
className="icon icon-archive-arrow-up-outline"
/>
<FormattedMessage
defaultMessage="Unarchive Team"
id="admin.team_settings.team_details.unarchiveTeam"
>
<span>
Unarchive Team
</span>
</FormattedMessage>
</button>
</div>
</OverlayTrigger>
</OverlayTrigger>
</OverlayTrigger>
</WithTooltip>
<button
className="btn btn-secondary upgrade-options-button"
onClick={[Function]}

View File

@ -16,13 +16,3 @@
display: inline-block;
cursor: not-allowed;
}
.tooltip-title {
font-weight: 600;
}
.tooltip-body {
font-weight: normal;
opacity: 56%;
text-align: left;
}

View File

@ -16,11 +16,10 @@ import {openModal} from 'actions/views/modals';
import useGetUsage from 'components/common/hooks/useGetUsage';
import useGetUsageDeltas from 'components/common/hooks/useGetUsageDeltas';
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import OverlayTrigger from 'components/overlay_trigger';
import PricingModal from 'components/pricing_modal';
import Tooltip from 'components/tooltip';
import AdminPanel from 'components/widgets/admin_console/admin_panel';
import TeamIcon from 'components/widgets/team_icon/team_icon';
import WithTooltip from 'components/with_tooltip';
import {ModalIdentifiers} from 'utils/constants';
import {imageURLForTeam} from 'utils/utils';
@ -66,26 +65,11 @@ export function TeamProfile({team, isArchived, onToggleArchive, isDisabled, save
const button = () => {
if (restoreDisabled) {
return (
<OverlayTrigger
delay={400}
<WithTooltip
id='sharedTooltip'
title={defineMessage({id: 'workspace_limits.teams_limit_reached.upgrade_to_unarchive', defaultMessage: 'Upgrade to Unarchive'})}
hint={defineMessage({id: 'workspace_limits.teams_limit_reached.tool_tip', defaultMessage: 'You\'ve reached the team limit for your current plan. Consider upgrading to unarchive this team or archive your other teams'})}
placement='bottom'
disabled={!restoreDisabled}
overlay={
<Tooltip id='sharedTooltip'>
<div className={'tooltip-title'}>
<FormattedMessage
id={'workspace_limits.teams_limit_reached.upgrade_to_unarchive'}
defaultMessage={'Upgrade to Unarchive'}
/>
</div>
<div className={'tooltip-body'}>
<FormattedMessage
id={'workspace_limits.teams_limit_reached.tool_tip'}
defaultMessage={'You\'ve reached the team limit for your current plan. Consider upgrading to unarchive this team or archive your other teams'}
/>
</div>
</Tooltip>
}
>
{/* OverlayTrigger doesn't play nicely with `disabled` buttons, because the :hover events don't fire. This is a workaround to ensure the popover appears see: https://github.com/react-bootstrap/react-bootstrap/issues/1588*/}
<div
@ -116,8 +100,7 @@ export function TeamProfile({team, isArchived, onToggleArchive, isDisabled, save
<FormattedMessage {...archiveBtn}/>
</button>
</div>
</OverlayTrigger>
</WithTooltip>
);
}
return (

View File

@ -3,7 +3,7 @@
import classNames from 'classnames';
import React from 'react';
import {FormattedMessage, defineMessages} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import {AlertOutlineIcon} from '@mattermost/compass-icons/components';
@ -11,17 +11,15 @@ import StatisticCount from 'components/analytics/statistic_count';
import {calculateOverageUserActivated} from 'utils/overage_team';
import Title from './title';
type ActivatedUserCardProps = {
seatsPurchased: number;
activatedUsers: number | undefined;
isCloud: boolean;
}
export const messages = defineMessages({
totalUsers: {id: 'analytics.team.totalUsers', defaultMessage: 'Total Active Users'},
});
export const ActivatedUserCard = ({activatedUsers, seatsPurchased, isCloud}: ActivatedUserCardProps) => {
const ActivatedUserCard = ({activatedUsers, seatsPurchased, isCloud}: ActivatedUserCardProps) => {
const {isBetween5PercerntAnd10PercentPurchasedSeats, isOver10PercerntPurchasedSeats} = calculateOverageUserActivated({seatsPurchased, activeUsers: activatedUsers || 0});
const showOverageWarning = !isCloud && (isBetween5PercerntAnd10PercentPurchasedSeats || isOver10PercerntPurchasedSeats);
@ -36,11 +34,7 @@ export const ActivatedUserCard = ({activatedUsers, seatsPurchased, isCloud}: Act
return (
<StatisticCount
title={
<FormattedMessage
{...messages.totalUsers}
/>
}
title={<Title/>}
icon='fa-users'
status={activeUserStatus}
count={activatedUsers}
@ -67,3 +61,5 @@ export const ActivatedUserCard = ({activatedUsers, seatsPurchased, isCloud}: Act
</StatisticCount>
);
};
export default ActivatedUserCard;

View File

@ -0,0 +1,35 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {defineMessage, defineMessages, useIntl} from 'react-intl';
import {InformationOutlineIcon} from '@mattermost/compass-icons/components';
import ExternalLink from 'components/external_link';
import WithTooltip from 'components/with_tooltip';
export const messages = defineMessages({
totalUsers: {id: 'analytics.team.totalUsers', defaultMessage: 'Total Activated Users'},
});
const Title = () => {
const intl = useIntl();
return (
<WithTooltip
id='activated_user_title_tooltip'
title={defineMessage({id: 'analytics.team.totalUsers.title.tooltip.title', defaultMessage: 'Activated users on this server'})}
hint={defineMessage({id: 'analytics.team.totalUsers.title.tooltip.hint', defaultMessage: 'Also called Registered Users'})}
placement='top'
>
<span>
<ExternalLink href='https://mattermost.com/pl/site-statistics-definitions'>
{intl.formatMessage(messages.totalUsers)}
<InformationOutlineIcon size='16'/>
</ExternalLink>
</span>
</WithTooltip>
);
};
export default Title;

View File

@ -9,7 +9,7 @@ import type {ClientLicense} from '@mattermost/types/config';
import * as AdminActions from 'actions/admin_actions.jsx';
import {ActivatedUserCard} from 'components/analytics/activated_users_card';
import ActivatedUserCard from 'components/analytics/activated_users_card';
import TrueUpReview from 'components/analytics/true_up_review';
import ExternalLink from 'components/external_link';
import AdminHeader from 'components/widgets/admin_console/admin_header';

View File

@ -16,7 +16,8 @@ import {General} from 'mattermost-redux/constants';
import * as AdminActions from 'actions/admin_actions';
import Banner from 'components/admin_console/banner';
import {ActivatedUserCard, messages as activatedUsersCardsMessages} from 'components/analytics/activated_users_card';
import ActivatedUserCard from 'components/analytics/activated_users_card';
import {messages as activatedUsersCardsMessages} from 'components/analytics/activated_users_card/title';
import LineChart from 'components/analytics/line_chart';
import StatisticCount from 'components/analytics/statistic_count';
import TableChart from 'components/analytics/table_chart';

View File

@ -19,6 +19,9 @@ type Props = OverlayTriggerProps & {
className?: string;
};
/**
* @deprecated Use (and expand when extrictly needed) WithTooltip instead
*/
const OverlayTrigger = React.forwardRef((props: Props, ref?: React.Ref<OriginalOverlayTrigger>) => {
const {overlay, disabled, ...otherProps} = props;

View File

@ -42,3 +42,20 @@
}
}
}
.console__body {
.shortcut-key {
display: inline-block;
border-radius: 4px;
&.shortcut-key--tooltip {
padding: 2px 5px;
background-color: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.72);
font-family: inherit;
font-size: 12px;
font-weight: 600;
line-height: 16px;
}
}
}

View File

@ -14,6 +14,9 @@ type Props = {
placement?: string;
};
/**
* @deprecated Use (and expand when extrictly needed) WithTooltip instead
*/
export default function Tooltip(props: Props) {
return (
<RBTooltip

View File

@ -0,0 +1,58 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {ComponentProps} from 'react';
import type {MessageDescriptor} from 'react-intl';
import RenderEmoji from 'components/emoji/render_emoji';
import {ShortcutKey, ShortcutKeyVariant} from 'components/shortcut_key';
import Tooltip from 'components/tooltip';
import {getStringOrDescriptorComponent} from './utils';
export type CommonTooltipProps = {
id: string;
title: string | MessageDescriptor;
hint?: string | MessageDescriptor;
shortcut?: string[];
emoji?: string;
}
export function createTooltip(commonTooltipProps: CommonTooltipProps) {
return (props: Omit<ComponentProps<typeof Tooltip>, 'children' | 'id'>) => {
const title = getStringOrDescriptorComponent(commonTooltipProps.title);
const hint = getStringOrDescriptorComponent(commonTooltipProps.hint);
const emoji = commonTooltipProps.emoji && (
<RenderEmoji
emojiName={commonTooltipProps.emoji}
size={12}
/>
);
return (
<Tooltip
{...props}
id={commonTooltipProps.id}
>
<div className={'tooltip-title'}>
{emoji}
{title}
</div>
{commonTooltipProps.shortcut && (
<div className={'tooltip-shortcuts-container'}>
{commonTooltipProps.shortcut.map((v) => (
<ShortcutKey
key={v}
variant={ShortcutKeyVariant.Tooltip}
>
{v}
</ShortcutKey>
))}
</div>
)}
{commonTooltipProps.hint && (<div className={'tooltip-hint'}>{hint}</div>)}
</Tooltip>
);
};
}

View File

@ -0,0 +1,48 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useMemo} from 'react';
import type {ComponentProps} from 'react';
import OverlayTrigger from 'components/overlay_trigger';
import Constants from 'utils/constants';
import type {CommonTooltipProps} from './create_tooltip';
import {createTooltip} from './create_tooltip';
type OverlayTriggerProps = ComponentProps<typeof OverlayTrigger>;
type WithTooltipProps = {
children: OverlayTriggerProps['children'];
placement: OverlayTriggerProps['placement'];
} & CommonTooltipProps;
const WithTooltip = ({
id,
title,
emoji,
hint,
shortcut,
placement,
children,
}: WithTooltipProps) => {
const ThisTooltip = useMemo(() => createTooltip({
id,
title,
emoji,
hint,
shortcut,
}), [id, title, emoji, hint, shortcut]);
return (
<OverlayTrigger
delay={Constants.OVERLAY_TIME_DELAY}
overlay={<ThisTooltip/>}
placement={placement}
>
{children}
</OverlayTrigger>
);
};
export default WithTooltip;

View File

@ -0,0 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {ComponentProps} from 'react';
import type {MessageDescriptor} from 'react-intl';
import {FormattedMessage} from 'react-intl';
export function getStringOrDescriptorComponent(v: string | MessageDescriptor | undefined, values?: ComponentProps<typeof FormattedMessage>['values']) {
if (!v) {
return undefined;
}
if (typeof v === 'string') {
return v;
}
return (
<FormattedMessage
{...v}
values={values}
/>
);
}

View File

@ -2713,7 +2713,9 @@
"analytics.team.recentUsers": "Recent Active Users",
"analytics.team.title": "Team Statistics for {team}",
"analytics.team.totalPosts": "Total Posts",
"analytics.team.totalUsers": "Total Active Users",
"analytics.team.totalUsers": "Total Activated Users",
"analytics.team.totalUsers.title.tooltip.hint": "Also called Registered Users",
"analytics.team.totalUsers.title.tooltip.title": "Activated users on this server",
"announcement_bar.error.email_verification_required": "Check your email inbox to verify the address.",
"announcement_bar.error.license_expired": "{licenseSku} license is expired and some features may be disabled.",
"announcement_bar.error.license_expiring": "{licenseSku} license expires on {date, date, long}.",

View File

@ -49,6 +49,29 @@
.tooltip-help {
color: rgba(255, 255, 255, 0.64);
}
.tooltip-title {
font-weight: 600;
line-height: 15px;
}
.tooltip-hint {
font-size: 11px;
line-height: 16px;
opacity: 56%;
}
.tooltip-shortcuts-container {
display: flex;
justify-content: center;
padding: 4px 0;
gap: 2px;
}
.emoticon {
margin-right: 6px;
vertical-align: center;
}
}
.floating-ui-tooltip {

View File

@ -10,6 +10,10 @@
}
}
.analytics_tooltip_body {
font-weight: 400;
}
.team_statistics {
&--warning {
// Adding the !important because this class has less preference in some html elements
@ -71,6 +75,11 @@
width: calc(100% - 20px);
text-overflow: ellipsis;
white-space: nowrap;
svg {
margin-left: 5px;
vertical-align: text-top;
}
}
.fa {