Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 75 additions & 120 deletions static/app/components/core/badge/alertBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import type {Theme} from '@emotion/react';
import {useTheme} from '@emotion/react';
import {useTheme, type Theme} from '@emotion/react';
import styled from '@emotion/styled';

import {Flex} from 'sentry/components/core/layout';
import {
IconCheckmark,
IconExclamation,
IconFire,
IconIssues,
IconPause,
} from 'sentry/icons';
import {IconCheckmark, IconFire, IconIssues, IconPause, IconWarning} from 'sentry/icons';
import type {SVGIconProps} from 'sentry/icons/svgIcon';
import {t} from 'sentry/locale';
import {IncidentStatus} from 'sentry/views/alerts/types';
Expand All @@ -33,135 +26,97 @@ interface AlertBadgeProps {
withText?: boolean;
}

type AlertBadgeStatus = IncidentStatus | 'disabled' | 'issue';
interface AlertBadgeConfig {
icon: React.FC<SVGIconProps>;
style: React.CSSProperties;
text: string;
}
function getAlertBadgeConfig(status: AlertBadgeStatus, theme: Theme): AlertBadgeConfig {
switch (status) {
case 'disabled':
return {
text: t('Disabled'),
icon: IconPause,
style: {
color: theme.tokens.content.primary,
background: theme.tokens.background.primary,
border: `1px solid ${theme.tokens.border.primary}`,
},
};
case 'issue':
return {
text: t('Issue'),
icon: IconIssues,
style: {
color: theme.tokens.content.primary,
background: theme.tokens.background.primary,
border: `1px solid ${theme.tokens.border.primary}`,
},
};
case IncidentStatus.CRITICAL:
return {
text: t('Critical'),
icon: IconFire,
style: {
color: theme.tokens.content.onVibrant.light,
background: theme.tokens.background.danger.vibrant,
},
};
case IncidentStatus.WARNING:
return {
text: t('Warning'),
icon: IconWarning,
style: {
color: theme.tokens.content.onVibrant.dark,
background: theme.tokens.background.warning.vibrant,
},
};
default:
return {
text: t('Resolved'),
icon: IconCheckmark,
style: {
color: theme.tokens.content.onVibrant.dark,
background: theme.tokens.background.success.vibrant,
},
};
}
}

/**
* This badge is a composition of DiamondStatus specifically used for incident
* alerts.
*/
export function AlertBadge(props: AlertBadgeProps) {
const theme = useTheme();
const {text, icon: Icon} = getDiamondTheme(
props.status,
props.isIssue,
props.isDisabled,
theme
);
const status = props.isDisabled
? 'disabled'
: props.isIssue
? 'issue'
: (props.status ?? IncidentStatus.CLOSED);
const {text, icon: Icon, style} = getAlertBadgeConfig(status, theme);

return (
<PaddedContainer data-test-id="alert-badge" align="center" gap="lg">
<DiamondBackground
{...props}
<Flex
align="center"
justify="center"
role="presentation"
width="26px"
height="26px"
radius="xs"
aria-label={props.withText ? undefined : text}
style={style}
>
<Icon width={13} height={13} />
</DiamondBackground>
</Flex>
{props.withText && <div>{text}</div>}
</PaddedContainer>
);
}

function getDiamondTheme(
status: AlertBadgeProps['status'],
isIssue: AlertBadgeProps['isIssue'],
isDisabled: AlertBadgeProps['isDisabled'],
theme: Theme
): {
backgroundColor: string;
icon: React.ComponentType<SVGIconProps>;
text: string;
} {
if (isDisabled) {
return {
text: t('Disabled'),
backgroundColor: theme.tokens.content.disabled,
icon: IconPause,
};
}
if (isIssue) {
return {
text: t('Issue'),
backgroundColor: theme.subText,
// @TODO(jonasbadalic): why does the issues icon height need to be adjusted?
icon: (props: SVGIconProps) => <IconIssues width={13} height={13} {...props} />,
};
}
if (status === IncidentStatus.CRITICAL) {
return {text: t('Critical'), backgroundColor: theme.errorText, icon: IconFire};
}
if (status === IncidentStatus.WARNING) {
return {
text: t('Warning'),
backgroundColor: theme.tokens.content.warning,
icon: IconExclamation,
};
}
return {
text: t('Resolved'),
backgroundColor: theme.tokens.content.success,
icon: IconCheckmark,
};
}

function makeAlertBadgeDiamondBackgroundTheme(
status: AlertBadgeProps['status'],
isIssue: AlertBadgeProps['isIssue'],
isDisabled: AlertBadgeProps['isDisabled'],
theme: Theme
): React.CSSProperties {
if (isDisabled) {
return {
color: theme.tokens.content.primary,
background: theme.colors.surface500,
border: `1px solid ${theme.colors.surface100}`,
};
}
if (isIssue) {
return {
color: theme.tokens.content.primary,
background: theme.colors.surface500,
border: `1px solid ${theme.colors.surface100}`,
};
}
if (status === IncidentStatus.CRITICAL) {
return {
color: theme.colors.white,
background: theme.colors.chonk.red400,
border: `1px solid ${theme.colors.red100}`,
};
}
if (status === IncidentStatus.WARNING) {
return {
color: theme.colors.black,
background: theme.colors.chonk.yellow400,
border: `1px solid ${theme.colors.yellow100}`,
};
}
return {
color: theme.colors.black,
background: theme.colors.chonk.green400,
border: `1px solid ${theme.colors.green100}`,
};
}

const PaddedContainer = styled(Flex)`
/* @TODO(jonasbadalic): This used to be sized by the oversized icon inside it */
padding: 5px 4px;
`;

const DiamondBackground = styled('div')<AlertBadgeProps>`
${p => ({
...makeAlertBadgeDiamondBackgroundTheme(p.status, p.isIssue, p.isDisabled, p.theme),
})};

width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
border-radius: ${p => p.theme.radius.xs};

> svg {
width: 13px;
height: 13px;
}
padding: calc(${p => p.theme.space.xs} + 1px) ${p => p.theme.space.xs};
`;
Loading