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  After  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  After  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:
parent
1871106d0a
commit
f23ae99998
18 changed files with 239 additions and 194 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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')}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -196,3 +196,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-badge {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
@ -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>) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,3 @@
|
|||
flex-grow: 1;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.schedules__user-on-call {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue