Add truncation for content beyond 2 lines and show tooltip with original value if content was truncated (#3123)

# What this PR does

- Removed `MAX_LINE_LENGTH` which was used to truncate Integrations Name
- Added new component `TextEllipsisTooltip` that determines if its
content was truncated, and if so, it will show a tooltip with the
original value. Integrations Name and Alert Group Name fields inside
tables have been changed so that after filling in 2 lines, the content
gets truncated and a tooltip is shown on hover.

Before

![image](https://github.com/grafana/oncall/assets/40542072/1bdd29f3-5804-45f1-85b1-69657d26c73f)
After

![image](https://github.com/grafana/oncall/assets/40542072/1a68c3ed-5884-431f-8a6f-f8ea1185987c)

As you can see this results in more space being taken for the
integrations name (but no more than what it should take), and the end
result is always truncated after 2 lines. Same applies to the
Integrations

Before

![image](https://github.com/grafana/oncall/assets/40542072/802e9a4f-89b5-461d-9c04-15d7117cdb8b)
After

![image](https://github.com/grafana/oncall/assets/40542072/a0674469-bdcc-4051-9783-9ffc81b8ba31)

The `Don't delete` integration wasn't even showing 100% of its full
width in the before screen, but it shows in the after because now
there's no more limit on the characters.
This commit is contained in:
Rares Mardare 2023-10-18 14:41:14 +03:00 committed by GitHub
parent 1871106d0a
commit f23ae99998
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 239 additions and 194 deletions

View file

@ -24,6 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improve alert group deletion API by @vadimkerr ([#3124](https://github.com/grafana/oncall/pull/3124))
- Removed Integrations Name max characters limit
([#3123](https://github.com/grafana/oncall/pull/3123))
- Truncate long table rows (Integration Name/Alert Group) and show tooltip for the truncated content
([#3123](https://github.com/grafana/oncall/pull/3123))
## v1.3.42 (2023-10-04)

View file

@ -47,6 +47,9 @@
.rc-table-cell {
padding-left: 4px;
padding-right: 4px;
/* works better than break-all, especially for table headers */
word-break: break-word;
}
.grecaptcha-badge {

View file

@ -1,23 +0,0 @@
/*
Make sure if you chage max-width here
You also change it in consts.ts
*/
@media screen and (max-width: 1500px) {
.table__email-column {
max-width: 175px;
}
.table__email-content {
text-overflow: ellipsis;
overflow: hidden;
}
.incident__title-column {
overflow-wrap: anywhere;
white-space: pre-wrap;
}
}
.table__wrap-column {
word-break: break-word;
}

View file

@ -3,7 +3,7 @@
*/
.u-flex {
display: flex;
display: flex !important;
flex-direction: row;
}
@ -84,10 +84,6 @@
position: relative;
}
.u-overflow-x-auto {
overflow-x: auto;
}
.u-break-word {
word-break: break-word;
}
@ -129,3 +125,28 @@
margin-bottom: 0;
margin-right: 4px;
}
/* -----
* Overflow
*/
.u-overflow-x-auto {
overflow-x: auto;
}
.overflow-child {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
white-space: initial;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.break-word {
word-break: break-all;
}
.line-clamp-3 {
-webkit-line-clamp: 3;
}

View file

@ -1,53 +0,0 @@
import React, { FC, useEffect, useState } from 'react';
import { Tooltip } from '@grafana/ui';
import { debounce } from 'throttle-debounce';
interface MatchMediaTooltipProps {
placement: 'top' | 'bottom' | 'right' | 'left';
content: string;
children: JSX.Element;
maxWidth?: number;
minWidth?: number;
}
const DEBOUNCE_MS = 200;
export const MatchMediaTooltip: FC<MatchMediaTooltipProps> = ({ minWidth, maxWidth, placement, content, children }) => {
const [match, setMatch] = useState<MediaQueryList>(getMatch());
useEffect(() => {
const debouncedResize = debounce(DEBOUNCE_MS, onWindowResize);
window.addEventListener('resize', debouncedResize);
return () => {
window.removeEventListener('resize', debouncedResize);
};
}, []);
if (match?.matches) {
return (
<Tooltip placement={placement} content={content}>
{children}
</Tooltip>
);
}
return <>{children}</>;
function onWindowResize() {
setMatch(getMatch());
}
function getMatch() {
if (minWidth && maxWidth) {
return window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`);
} else if (minWidth) {
return window.matchMedia(`(min-width: ${minWidth}px)`);
} else if (maxWidth) {
return window.matchMedia(`(max-width: ${maxWidth}px)`);
}
return undefined;
}
};

View file

@ -0,0 +1,60 @@
import React, { useEffect, useRef, useState } from 'react';
import { Tooltip } from '@grafana/ui';
import cn from 'classnames/bind';
import styles from 'assets/style/utils.css';
import { TEXT_ELLIPSIS_CLASS } from 'utils/consts';
const cx = cn.bind(styles);
interface TextEllipsisTooltipProps {
content: string;
queryClassName?: string;
placement?: string;
className?: string;
children: JSX.Element | JSX.Element[];
}
const TextEllipsisTooltip: React.FC<TextEllipsisTooltipProps> = ({
queryClassName = TEXT_ELLIPSIS_CLASS,
className,
content: textContent,
placement,
children,
}) => {
const [isEllipsis, setIsEllipsis] = useState(true);
const elContentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setEllipsis();
}, []);
const elContent = (
<div className={cx(className)} ref={elContentRef} onMouseOver={setEllipsis}>
{children}
</div>
);
if (isEllipsis) {
return (
<Tooltip content={textContent} placement={placement as any}>
{/* The wrapping div is needed, otherwise the attached ref will be lost when <Tooltip /> mounts */}
<div>{elContent}</div>
</Tooltip>
);
}
return elContent;
function setEllipsis() {
const el = elContentRef?.current?.querySelector<HTMLElement>(`.${queryClassName}`);
if (!el) {
return;
}
setIsEllipsis(el.offsetHeight < el.scrollHeight);
}
};
export default TextEllipsisTooltip;

View file

@ -17,6 +17,7 @@ interface TooltipBadgeProps {
icon?: IconName;
customIcon?: React.ReactNode;
addPadding?: boolean;
placement?;
onHover?: () => void;
}
@ -24,14 +25,25 @@ interface TooltipBadgeProps {
const cx = cn.bind(styles);
const TooltipBadge: FC<TooltipBadgeProps> = (props) => {
const { borderType, text, tooltipTitle, tooltipContent, onHover, addPadding, icon, customIcon, className, ...rest } =
props;
const {
borderType,
text,
tooltipTitle,
tooltipContent,
placement,
onHover,
addPadding,
icon,
customIcon,
className,
...rest
} = props;
const testId = rest['data-testid'];
return (
<Tooltip
placement="bottom-start"
placement={placement || 'bottom-start'}
interactive
content={
<div className={cx('tooltip')}>

View file

@ -14,11 +14,12 @@ const cx = cn.bind(styles);
interface TeamNameProps {
team: GrafanaTeam;
className?: string;
size?: 'small' | 'medium' | 'large';
}
const TeamName = observer((props: TeamNameProps) => {
const { team, size = 'medium' } = props;
const { team, size = 'medium', className } = props;
if (!team) {
return null;
}
@ -26,7 +27,7 @@ const TeamName = observer((props: TeamNameProps) => {
return <Badge text={team.name} color={'blue'} tooltip={'Resource is not assigned to any team (ex General team)'} />;
}
return (
<Text type="secondary" size={size}>
<Text type="secondary" size={size} className={className}>
<Avatar size="small" src={team.avatar_url} className={cx('avatar')} />
<Tooltip placement="top" content={'Resource is assigned to ' + team.name}>
<Text type="primary">{team.name}</Text>

View file

@ -4,10 +4,10 @@ import { Button, HorizontalGroup, IconButton, Tooltip, VerticalGroup } from '@gr
import cn from 'classnames/bind';
import Avatar from 'components/Avatar/Avatar';
import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip';
import PluginLink from 'components/PluginLink/PluginLink';
import Tag from 'components/Tag/Tag';
import Text from 'components/Text/Text';
import TextEllipsisTooltip from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { Alert as AlertType, Alert, IncidentStatus } from 'models/alertgroup/alertgroup.types';
import { User } from 'models/user/user.types';
@ -15,7 +15,7 @@ import { SilenceButtonCascader } from 'pages/incidents/parts/SilenceButtonCascad
import { move } from 'state/helpers';
import { getVar } from 'utils/DOM';
import { UserActions } from 'utils/authorization';
import { TABLE_COLUMN_MAX_WIDTH } from 'utils/consts';
import { TEXT_ELLIPSIS_CLASS } from 'utils/consts';
import styles from './Incident.module.scss';
@ -78,14 +78,13 @@ export function renderRelatedUsers(incident: Alert, isFull = false) {
}
return (
<PluginLink key={user.pk} query={{ page: 'users', id: user.pk }} wrap={false} className="table__email-content">
<Text type="secondary">
<Avatar size="small" src={user.avatar} />{' '}
<MatchMediaTooltip placement="top" content={user.username} maxWidth={TABLE_COLUMN_MAX_WIDTH}>
<span>{user.username}</span>
</MatchMediaTooltip>{' '}
{badge}
</Text>
<PluginLink key={user.pk} query={{ page: 'users', id: user.pk }} wrap={false}>
<TextEllipsisTooltip placement="top" content={user.username}>
<Text type="secondary" className={cx(TEXT_ELLIPSIS_CLASS)}>
<Avatar size="small" src={user.avatar} /> <span className={cx('break-word')}>{user.username}</span>
<span className={cx('user-badge')}>{badge}</span>
</Text>
</TextEllipsisTooltip>
</PluginLink>
);
}
@ -117,32 +116,30 @@ export function renderRelatedUsers(incident: Alert, isFull = false) {
}
return (
<div className={'table__email-column'}>
<VerticalGroup spacing="xs">
{visibleUsers.map(renderUser)}
{Boolean(otherUsers.length) && (
<Tooltip
placement="top"
content={
<>
{otherUsers.map((user, index) => (
<>
{index ? ', ' : ''}
{renderUser(user)}
</>
))}
</>
}
>
<span>
<Text type="secondary" underline size="small">
+{otherUsers.length} user{otherUsers.length > 1 ? 's' : ''}
</Text>
</span>
</Tooltip>
)}
</VerticalGroup>
</div>
<VerticalGroup spacing="xs">
{visibleUsers.map(renderUser)}
{Boolean(otherUsers.length) && (
<Tooltip
placement="top"
content={
<>
{otherUsers.map((user, index) => (
<>
{index ? ', ' : ''}
{renderUser(user)}
</>
))}
</>
}
>
<span>
<Text type="secondary" underline size="small">
+{otherUsers.length} user{otherUsers.length > 1 ? 's' : ''}
</Text>
</span>
</Tooltip>
)}
</VerticalGroup>
);
}

View file

@ -196,3 +196,7 @@
}
}
}
.user-badge {
vertical-align: middle;
}

View file

@ -1,6 +1,6 @@
import React, { ReactElement, SyntheticEvent } from 'react';
import { Button, HorizontalGroup, Icon, LoadingPlaceholder, Tooltip, VerticalGroup } from '@grafana/ui';
import { Button, HorizontalGroup, Icon, LoadingPlaceholder, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { get } from 'lodash-es';
import { observer } from 'mobx-react';
@ -15,6 +15,7 @@ import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo';
import ManualAlertGroup from 'components/ManualAlertGroup/ManualAlertGroup';
import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import TextEllipsisTooltip from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
import Tutorial from 'components/Tutorial/Tutorial';
import { TutorialStep } from 'components/Tutorial/Tutorial.types';
import { IncidentsFiltersType } from 'containers/IncidentsFilters/IncidentFilters.types';
@ -27,7 +28,7 @@ import { PageProps, WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import LocationHelper from 'utils/LocationHelper';
import { UserActions } from 'utils/authorization';
import { PAGE, PLUGIN_ROOT } from 'utils/consts';
import { PAGE, PLUGIN_ROOT, TEXT_ELLIPSIS_CLASS } from 'utils/consts';
import styles from './Incidents.module.scss';
import { IncidentDropdown } from './parts/IncidentDropdown';
@ -463,7 +464,7 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
const columns = [
{
width: '5%',
width: '140px',
title: 'Status',
key: 'time',
render: withSkeleton(this.renderStatus),
@ -553,7 +554,13 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
};
renderId(record: AlertType) {
return <Text type="secondary">#{record.inside_organization_number}</Text>;
return (
<TextEllipsisTooltip placement="top" content={`#${record.inside_organization_number}`}>
<Text type="secondary" className={cx(TEXT_ELLIPSIS_CLASS)}>
#{record.inside_organization_number}
</Text>
</TextEllipsisTooltip>
);
}
renderTitle = (record: AlertType) => {
@ -565,25 +572,25 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
const { incidentsItemsPerPage, incidentsCursor } = store.alertGroupStore;
return (
<VerticalGroup spacing="none" justify="center">
<div className={'table__wrap-column'}>
<PluginLink
query={{
page: 'alert-groups',
id: record.pk,
cursor: incidentsCursor,
perpage: incidentsItemsPerPage,
start,
...query,
}}
>
<Tooltip placement="top" content={record.render_for_web.title}>
<span>{record.render_for_web.title}</span>
</Tooltip>
</PluginLink>
{Boolean(record.dependent_alert_groups.length) && ` + ${record.dependent_alert_groups.length} attached`}
</div>
</VerticalGroup>
<div>
<TextEllipsisTooltip placement="top" content={record.render_for_web.title}>
<Text type="link" size="medium" className={cx('overflow-parent')}>
<PluginLink
query={{
page: 'alert-groups',
id: record.pk,
cursor: incidentsCursor,
perpage: incidentsItemsPerPage,
start,
...query,
}}
>
<Text className={cx(TEXT_ELLIPSIS_CLASS)}>{record.render_for_web.title}</Text>
</PluginLink>
</Text>
</TextEllipsisTooltip>
{Boolean(record.dependent_alert_groups.length) && ` + ${record.dependent_alert_groups.length} attached`}
</div>
);
};
@ -598,10 +605,14 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
const integration = alertReceiveChannelStore.getIntegration(record.alert_receive_channel);
return (
<HorizontalGroup spacing="sm">
<TextEllipsisTooltip
className={cx('u-flex', 'u-flex-gap-xs', 'overflow-parent')}
placement="top"
content={record?.alert_receive_channel?.verbal_name || ''}
>
<IntegrationLogo integration={integration} scale={0.1} />
<Emoji text={record.alert_receive_channel?.verbal_name || ''} />
</HorizontalGroup>
<Emoji className={cx(TEXT_ELLIPSIS_CLASS)} text={record.alert_receive_channel?.verbal_name || ''} />
</TextEllipsisTooltip>
);
};
@ -631,7 +642,11 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
}
renderTeam(record: AlertType, teams: any) {
return <TeamName team={teams[record.team]} />;
return (
<TextEllipsisTooltip placement="top" content={teams[record.team]?.name}>
<TeamName className={TEXT_ELLIPSIS_CLASS} team={teams[record.team]} />
</TextEllipsisTooltip>
);
}
getOnActionButtonClick = (incidentId: string, action: AlertAction): ((e: SyntheticEvent) => Promise<void>) => {

View file

@ -2,7 +2,12 @@
width: 180px;
}
.title {
.heartbeat-badge {
padding: 4px 10px;
width: 40px;
}
.integrations-header {
margin-bottom: 24px;
right: 0;
}
@ -15,11 +20,6 @@
margin-top: 16px;
}
.heartbeat-badge {
padding: 4px 10px;
width: 40px;
}
.integrations-actionsList {
display: flex;
flex-direction: column;
@ -44,4 +44,4 @@
&:hover {
background: var(--cards-background);
}
}
}

View file

@ -19,6 +19,7 @@ import {
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import TextEllipsisTooltip from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu';
import IntegrationForm from 'containers/IntegrationForm/IntegrationForm';
@ -34,14 +35,13 @@ import { withMobXProviderContext } from 'state/withStore';
import { openNotification } from 'utils';
import LocationHelper from 'utils/LocationHelper';
import { UserActions } from 'utils/authorization';
import { PAGE } from 'utils/consts';
import { PAGE, TEXT_ELLIPSIS_CLASS } from 'utils/consts';
import styles from './Integrations.module.scss';
const cx = cn.bind(styles);
const FILTERS_DEBOUNCE_MS = 500;
const ITEMS_PER_PAGE = 15;
const MAX_LINE_LENGTH = 40;
interface IntegrationsState extends PageBaseState {
integrationsFilters: Filters;
@ -227,16 +227,11 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
...query,
}}
>
<Text type="link" size="medium">
<Emoji
className={cx('title')}
text={
item.verbal_name?.length > MAX_LINE_LENGTH
? item.verbal_name?.substring(0, MAX_LINE_LENGTH) + '...'
: item.verbal_name
}
/>
</Text>
<TextEllipsisTooltip placement="top" content={item.verbal_name}>
<Text type="link" size="medium">
<Emoji className={cx('title', TEXT_ELLIPSIS_CLASS)} text={item.verbal_name} />
</Text>
</TextEllipsisTooltip>
</PluginLink>
);
};
@ -278,6 +273,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
<PluginLink query={{ page: 'incidents', integration: item.id }} className={cx('alertsInfoText')}>
<TooltipBadge
borderType="primary"
placement="top"
text={alertReceiveChannelCounter?.alerts_count + '/' + alertReceiveChannelCounter?.alert_groups_count}
tooltipTitle=""
tooltipContent={
@ -298,6 +294,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
icon="link"
text={`${connectedEscalationsChainsCount}/${routesCounter}`}
tooltipContent={undefined}
placement="top"
tooltipTitle={
connectedEscalationsChainsCount +
' connected escalation chain' +
@ -328,6 +325,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
<TooltipBadge
text={undefined}
className={cx('heartbeat-badge')}
placement="top"
borderType={heartbeatStatus ? 'success' : 'danger'}
customIcon={heartbeatStatus ? <HeartIcon /> : <HeartRedIcon />}
tooltipTitle={`Last heartbeat: ${heartbeat?.last_heartbeat_time_verbal}`}
@ -347,6 +345,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
<TooltipBadge
borderType="primary"
icon="pause"
placement="top"
text={IntegrationHelper.getMaintenanceText(item.maintenance_till)}
tooltipTitle={IntegrationHelper.getMaintenanceText(item.maintenance_till, maintenanceMode)}
tooltipContent={undefined}
@ -359,7 +358,11 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
}
renderTeam(item: AlertReceiveChannel, teams: any) {
return <TeamName team={teams[item.team]} />;
return (
<TextEllipsisTooltip placement="top" content={teams[item.team]?.name}>
<TeamName className={TEXT_ELLIPSIS_CLASS} team={teams[item.team]} />
</TextEllipsisTooltip>
);
}
renderButtons = (item: AlertReceiveChannel) => {

View file

@ -26,6 +26,7 @@ import {
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import TextEllipsisTooltip from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
import OutgoingWebhookForm from 'containers/OutgoingWebhookForm/OutgoingWebhookForm';
import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
import TeamName from 'containers/TeamName/TeamName';
@ -36,7 +37,7 @@ import { PageProps, WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import { openErrorNotification, openNotification } from 'utils';
import { isUserActionAllowed, UserActions } from 'utils/authorization';
import { PAGE, PLUGIN_ROOT } from 'utils/consts';
import { PAGE, PLUGIN_ROOT, TEXT_ELLIPSIS_CLASS } from 'utils/consts';
import styles from './OutgoingWebhooks.module.scss';
import { WebhookFormActionType } from './OutgoingWebhooks.types';
@ -246,7 +247,11 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
};
renderTeam(record: OutgoingWebhook, teams: any) {
return <TeamName team={teams[record.team]} />;
return (
<TextEllipsisTooltip placement="top" content={teams[record.team]?.name}>
<TeamName className={TEXT_ELLIPSIS_CLASS} team={teams[record.team]} />
</TextEllipsisTooltip>
);
}
renderActionButtons = (record: OutgoingWebhook) => {
@ -342,9 +347,13 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
renderUrl(url: string) {
return (
<div className="u-break-word">
<span>{url}</span>
</div>
<TextEllipsisTooltip content={url} placement="top">
<CopyToClipboard text={url} onCopy={() => openNotification('URL has been copied')}>
<Text type="link" className={cx(TEXT_ELLIPSIS_CLASS, 'line-clamp-3')}>
{url}
</Text>
</CopyToClipboard>
</TextEllipsisTooltip>
);
}

View file

@ -35,9 +35,3 @@
flex-grow: 1;
gap: 8px;
}
.schedules__user-on-call {
display: flex;
flex-wrap: nowrap;
gap: 4px;
}

View file

@ -9,11 +9,11 @@ import qs from 'query-string';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import Avatar from 'components/Avatar/Avatar';
import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip';
import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelector';
import PluginLink from 'components/PluginLink/PluginLink';
import Table from 'components/Table/Table';
import Text from 'components/Text/Text';
import TextEllipsisTooltip from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
import UserTimezoneSelect from 'components/UserTimezoneSelect/UserTimezoneSelect';
@ -33,7 +33,7 @@ import { WithStoreProps, PageProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import LocationHelper from 'utils/LocationHelper';
import { UserActions } from 'utils/authorization';
import { PAGE, PLUGIN_ROOT, TABLE_COLUMN_MAX_WIDTH } from 'utils/consts';
import { PAGE, PLUGIN_ROOT, TEXT_ELLIPSIS_CLASS } from 'utils/consts';
import styles from './Schedules.module.css';
@ -369,14 +369,14 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
{item.on_call_now.map((user) => {
return (
<PluginLink key={user.pk} query={{ page: 'users', id: user.pk }} className="table__email-content">
<div className={cx('schedules__user-on-call')}>
<div>
<Avatar size="medium" src={user.avatar} />
</div>
<MatchMediaTooltip placement="top" content={user.username} maxWidth={TABLE_COLUMN_MAX_WIDTH}>
<span className="table__email-content">{user.username}</span>
</MatchMediaTooltip>
</div>
<HorizontalGroup>
<TextEllipsisTooltip placement="top" content={user.username}>
<Text type="secondary" className={cx(TEXT_ELLIPSIS_CLASS)}>
<Avatar size="small" src={user.avatar} />{' '}
<span className={cx('break-word')}>{user.username}</span>
</Text>
</TextEllipsisTooltip>
</HorizontalGroup>
</PluginLink>
);
})}

View file

@ -53,7 +53,6 @@ dayjs.extend(customParseFormat);
import 'assets/style/vars.css';
import 'assets/style/global.css';
import 'assets/style/utils.css';
import 'assets/style/responsive.css';
import { getQueryParams, isTopNavbar } from './GrafanaPluginRootPage.helpers';
import PluginSetup from './PluginSetup';

View file

@ -41,9 +41,6 @@ export const FARO_ENDPOINT_PROD =
export const DOCS_SLACK_SETUP = 'https://grafana.com/docs/oncall/latest/open-source/#slack-setup';
export const DOCS_TELEGRAM_SETUP = 'https://grafana.com/docs/oncall/latest/notify/telegram/';
// Make sure if you chage max-width here you also change it in responsive.css
export const TABLE_COLUMN_MAX_WIDTH = 1500;
export const generateAssignToTeamInputDescription = (objectName: string): string =>
`Assigning to a team allows you to filter ${objectName} and configure their visibility. Go to OnCall -> Settings -> Team and Access Settings for more details.`;
@ -54,3 +51,5 @@ export enum PAGE {
Webhooks = 'webhooks',
Schedules = 'schedules',
}
export const TEXT_ELLIPSIS_CLASS = 'overflow-child';