Tag component tweaks to get rid of hardcoded tag variables (#4280)

# What this PR does

- Removed the usage of `var(--` within the Tag component to help get rid
of the vars file once we fully migrate to emotion
- Added few other display tweaks and migrated a few stylesheets to
emotion
This commit is contained in:
Rares Mardare 2024-04-29 11:13:26 +03:00 committed by GitHub
parent 0e59fadf38
commit 6ed7a1e3b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 445 additions and 433 deletions

View file

@ -174,6 +174,7 @@
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"throttle-debounce": "^2.1.0",
"tinycolor2": "^1.6.0",
"tslib": "2.5.3"
},
"packageManager": "yarn@1.22.21"

View file

@ -23,17 +23,15 @@ export const getCardButtonStyles = (theme: GrafanaTheme2) => {
`,
rootSelected: css`
{
&::before {
display: block;
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background-image: linear-gradient(270deg, #f55f3e 0%, #f83 100%);
}
&::before {
display: block;
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background-image: ${theme.colors.gradients.brandVertical};
}
`,
};

View file

@ -1,10 +1,9 @@
import React, { FC } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { Tag } from 'components/Tag/Tag';
import { Tag, TagColor } from 'components/Tag/Tag';
import { Text } from 'components/Text/Text';
interface IntegrationTagProps {
@ -15,7 +14,7 @@ export const IntegrationTag: FC<IntegrationTagProps> = ({ children }) => {
const styles = useStyles2(getStyles);
return (
<Tag className={styles.tag}>
<Tag className={styles.tag} color={TagColor.SECONDARY}>
<Text type="primary" size="small" className={styles.radius}>
{children}
</Text>
@ -23,11 +22,9 @@ export const IntegrationTag: FC<IntegrationTagProps> = ({ children }) => {
);
};
export const getStyles = (theme: GrafanaTheme2) => ({
export const getStyles = () => ({
tag: css({
height: '25px',
background: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.weak}`,
}),
radius: css({
borderRadius: '4px',

View file

@ -7,6 +7,7 @@ import { observer } from 'mobx-react';
import moment from 'moment-timezone';
import { SortableElement } from 'react-sortable-hoc';
import reactStringReplace from 'react-string-replace';
import { getLabelBackgroundTextColorObject } from 'styles/utils.styles';
import { PluginLink } from 'components/PluginLink/PluginLink';
import { Text } from 'components/Text/Text';
@ -28,7 +29,6 @@ import { UserGroup } from 'models/user_group/user_group.types';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { SelectOption, WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import { getVar } from 'utils/DOM';
import { UserActions } from 'utils/authorization/authorization';
import { DragHandle } from './DragHandle';
@ -80,12 +80,14 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
(escalationOption: EscalationPolicyOption) => escalationOption.value === step
);
const { textColor: itemTextColor } = getLabelBackgroundTextColorObject('green', this.props.theme);
return (
<Timeline.Item
key={id}
contentClassName={cx(this.styles.root)}
number={number}
textColor={isDisabled ? getVar('--tag-text-success') : undefined}
textColor={isDisabled ? itemTextColor : undefined}
backgroundClassName={backgroundClassName}
backgroundHexNumber={backgroundHexNumber}
>

View file

@ -17,27 +17,6 @@ export const getScheduleQualityStyles = (_theme: GrafanaTheme2) => {
tag: css`
font-size: 12px;
padding: 5px 10px;
&--danger {
// TODO: emotionjs
background-color: var(--tag-background-danger);
color: var(--tag-text-danger);
border: 1px solid var(--tag-border-danger);
}
&--warning {
// TODO: emotionjs
background-color: var(--tag-background-warning);
color: var(--tag-text-warning);
border: 1px solid var(--tag-border-warning);
}
&--primary {
// TODO: emotionjs
background-color: var(--tag-background-success);
color: var(--tag-text-success);
border: 1px solid var(--tag-border-success);
}
`,
};
};

View file

@ -3,11 +3,11 @@ import React, { FC, useEffect } from 'react';
import { cx } from '@emotion/css';
import { Tooltip, VerticalGroup, useStyles2 } from '@grafana/ui';
import { observer } from 'mobx-react';
import { bem, getUtilStyles } from 'styles/utils.styles';
import { getUtilStyles } from 'styles/utils.styles';
import { PluginLink } from 'components/PluginLink/PluginLink';
import { ScheduleQualityDetails } from 'components/ScheduleQualityDetails/ScheduleQualityDetails';
import { Tag } from 'components/Tag/Tag';
import { Tag, TagColor } from 'components/Tag/Tag';
import { Text } from 'components/Text/Text';
import { TooltipBadge } from 'components/TooltipBadge/TooltipBadge';
import { Schedule, ScheduleScoreQualityResult } from 'models/schedule/schedule.types';
@ -88,7 +88,7 @@ export const ScheduleQuality: FC<ScheduleQualityProps> = observer(({ schedule })
content={<ScheduleQualityDetails quality={quality} getScheduleQualityString={getScheduleQualityString} />}
>
<div className={cx(utils.cursorDefault)}>
<Tag className={cx(styles.tag, bem(styles.tag, getTagSeverity()))}>
<Tag className={cx(styles.tag)} color={getTagSeverity()}>
Quality: <strong>{getScheduleQualityString(quality.total_score)}</strong>
</Tag>
</div>
@ -115,11 +115,11 @@ export const ScheduleQuality: FC<ScheduleQualityProps> = observer(({ schedule })
function getTagSeverity() {
if (quality?.total_score < 20) {
return 'danger';
return TagColor.ERROR_LABEL;
}
if (quality?.total_score < 60) {
return 'warning';
return TagColor.WARNING_LABEL;
}
return 'primary';
return TagColor.SUCCESS_LABEL;
}
});

View file

@ -3,10 +3,10 @@ import React, { FC } from 'react';
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { bem } from 'styles/utils.styles';
import { bem, getLabelCss } from 'styles/utils.styles';
interface TagProps {
color?: string;
color?: string | TagColor;
className?: string;
border?: string;
text?: string;
@ -16,43 +16,89 @@ interface TagProps {
size?: 'small' | 'medium';
}
export enum TagColor {
SUCCESS = 'success',
WARNING = 'warning',
ERROR = 'error',
SECONDARY = 'secondary',
INFO = 'info',
SUCCESS_LABEL = 'successLabel',
WARNING_LABEL = 'warningLabel',
ERROR_LABEL = 'errorLabel',
}
export const Tag: FC<TagProps> = (props) => {
const { color, children, className, onClick, size = 'medium' } = props;
const styles = useStyles2(getStyles);
const { children, color, text, className, border, onClick, size = 'medium' } = props;
const style: React.CSSProperties = {
backgroundColor: color,
color: text,
border,
};
return (
<span
style={style}
className={cx(styles.root, bem(styles.root, size), className)}
className={cx(styles.root, bem(styles.root, size), getMatchingClass(), className)}
onClick={onClick}
ref={props.forwardedRef}
>
{children}
</span>
);
};
const getStyles = (_theme: GrafanaTheme2) => {
return {
root: css`
border-radius: 2px;
line-height: 100%;
padding: 5px 8px;
color: #fff;
display: inline-block;
white-space: nowrap;
`,
function getMatchingClass() {
return styles[color]
? // Either pick a defined style already or create one on the spot with the passed bgColor
styles[color]
: css`
background-color: ${color};
color: text;
`;
}
size: css`
&--small {
font-size: 12px;
height: 24px;
}
`,
};
function getStyles(theme: GrafanaTheme2) {
return {
root: css`
border-radius: 2px;
line-height: 100%;
padding: 5px 8px;
display: inline-block;
white-space: nowrap;
`,
size: css`
&--small {
font-size: 12px;
height: 24px;
}
`,
success: css`
background-color: ${theme.colors.success.main};
border: solid 1px ${theme.colors.success.main};
color: ${theme.isDark ? '#fff' : theme.colors.success.contrastText};
`,
warning: css`
background-color: ${theme.colors.warning.main};
border: solid 1px ${theme.colors.warning.main};
color: #fff;
`,
error: css`
background-color: ${theme.colors.error.main};
border: solid 1px ${theme.colors.error.main};
color: ${theme.isDark ? '#fff' : theme.colors.error.contrastText};
`,
secondary: css`
background-color: ${theme.colors.secondary.main};
border: solid 1px ${theme.colors.secondary.main};
color: ${theme.isDark ? '#fff' : theme.colors.secondary.contrastText};
`,
info: css`
background-color: ${theme.colors.primary.main};
border: solid 1px ${theme.colors.primary.main};
color: ${theme.isDark ? '#fff' : theme.colors.info.contrastText};
`,
successLabel: getLabelCss('green', theme),
warningLabel: getLabelCss('orange', theme),
errorLabel: getLabelCss('red', theme),
};
}
};

View file

@ -7,8 +7,8 @@ export const getTextStyles = (theme: GrafanaTheme2) => {
root: css`
display: inline;
&:hover .icon-button {
display: inline-block;
&:hover [data-emotion='iconButton'] {
display: inline-flex;
}
`,

View file

@ -91,8 +91,8 @@ export const Text: TextInterface = (props) => {
styles.root,
styles.text,
{ [styles.maxWidth]: Boolean(maxWidth) },
{ [bem(styles.text, `${type}`)]: true },
{ [bem(styles.text, `${size}`)]: true },
{ [bem(styles.text, type)]: true },
{ [bem(styles.text, size)]: true },
{ [bem(styles.text, `strong`)]: strong },
{ [bem(styles.text, `underline`)]: underline },
{ [bem(styles.text, 'clickable')]: clickable },
@ -109,6 +109,7 @@ export const Text: TextInterface = (props) => {
className={styles.iconButton}
tooltip="Edit"
tooltipPlacement="top"
data-emotion="iconButton"
name="pen"
/>
)}
@ -124,6 +125,7 @@ export const Text: TextInterface = (props) => {
className={styles.iconButton}
tooltip="Copy to clipboard"
tooltipPlacement="top"
data-emotion="iconButton"
name="copy"
/>
</CopyToClipboard>

View file

@ -1,8 +1,20 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { getLabelCss } from 'styles/utils.styles';
export const getTooltipBadgeStyles = (theme: GrafanaTheme2) => {
return {
primary: getLabelCss('blue', theme),
warning: getLabelCss('orange', theme),
success: getLabelCss('green', theme),
danger: getLabelCss('red', theme),
secondary: css`
background: ${theme.colors.background.secondary};
border: 1px solid ${theme.colors.border.weak};
color: ${theme.colors.text.primary};
`,
element: css`
font-size: 12px;
line-height: 16px;
@ -10,36 +22,6 @@ export const getTooltipBadgeStyles = (theme: GrafanaTheme2) => {
border-radius: 2px;
display: inline-block;
&--primary {
background: var(--tag-background-primary);
border: 1px solid var(--tag-border-primary);
color: var(--tag-text-primary);
}
&--secondary {
background: ${theme.colors.background.secondary};
border: 1px solid ${theme.colors.border.weak};
color: ${theme.colors.text.primary};
}
&--warning {
background: var(--tag-background-warning);
border: 1px solid var(--tag-border-warning);
color: var(--tag-text-warning);
}
&--success {
background: var(--tag-background-success);
border: 1px solid var(--tag-border-success);
color: var(--tag-text-success);
}
&--danger {
background: var(--tag-background-danger);
border: 1px solid var(--tag-border-danger);
color: var(--tag-text-danger);
}
&--padding {
padding: 3px 10px;
}

View file

@ -55,12 +55,7 @@ export const TooltipBadge: FC<TooltipBadgeProps> = (props) => {
}
>
<div
className={cx(
styles.element,
{ [bem(styles.element, `${borderType}`)]: true },
{ [bem(styles.element, 'padding')]: addPadding },
className
)}
className={cx(styles.element, styles[borderType], { [bem(styles.element, 'padding')]: addPadding }, className)}
onMouseEnter={onHover}
{...(testId ? { 'data-testid': testId } : {})}
>

View file

@ -20,7 +20,7 @@ exports[`Unauthorized renders properly - access control enabled: false 1`] = `
className="css-1fmhfo9"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -38,7 +38,7 @@ exports[`Unauthorized renders properly - access control enabled: false 1`] = `
className="css-u023fv"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -78,7 +78,7 @@ exports[`Unauthorized renders properly - access control enabled: true 1`] = `
className="css-1fmhfo9"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -96,7 +96,7 @@ exports[`Unauthorized renders properly - access control enabled: true 1`] = `
className="css-u023fv"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -136,7 +136,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Admin 1
className="css-1fmhfo9"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -154,7 +154,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Admin 1
className="css-u023fv"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -194,7 +194,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Editor
className="css-1fmhfo9"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -212,7 +212,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Editor
className="css-u023fv"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -252,7 +252,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Viewer
className="css-1fmhfo9"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,
@ -270,7 +270,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Viewer
className="css-u023fv"
>
<span
className="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
className="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
style={
{
"maxWidth": undefined,

View file

@ -19,7 +19,7 @@ exports[`AddResponders should properly display the add responders button when hi
class="css-u023fv"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
Participants
</span>
@ -72,7 +72,7 @@ exports[`AddResponders should properly display the add responders button when hi
class="css-u023fv"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
Participants
</span>
@ -106,7 +106,7 @@ exports[`AddResponders should render properly in create mode 1`] = `
class="css-u023fv"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
Participants
</span>
@ -159,7 +159,7 @@ exports[`AddResponders should render properly in update mode 1`] = `
class="css-u023fv"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
Participants
</span>
@ -212,7 +212,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
class="css-u023fv"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
Participants
</span>
@ -270,7 +270,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1287p17"
>
my test team
</span>
@ -323,7 +323,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1287p17"
>
my test user3
</span>
@ -438,7 +438,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1287p17"
>
my test user
</span>
@ -552,7 +552,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1287p17"
>
my test user2
</span>
@ -664,7 +664,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
class="css-9om60p"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
<a
class="learn-more-link"
@ -673,7 +673,7 @@ exports[`AddResponders should render selected team and users properly 1`] = `
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
<div
class="css-ffyaiw-horizontal-group"

View file

@ -31,7 +31,7 @@ exports[`TeamResponder it renders data properly 1`] = `
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1287p17"
>
my test team
</span>

View file

@ -31,7 +31,7 @@ exports[`UserResponder it renders data properly 1`] = `
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium responder-name css-1287p17"
>
johnsmith
</span>

View file

@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { VerticalGroup } from '@grafana/ui';
import { VerticalGroup, useTheme2 } from '@grafana/ui';
import { Timeline } from 'components/Timeline/Timeline';
import { MSTeamsConnector } from 'containers/AlertRules/parts/connectors/MSTeamsConnector';
@ -9,7 +9,6 @@ import { TelegramConnector } from 'containers/AlertRules/parts/connectors/Telegr
import { ChannelFilter } from 'models/channel_filter/channel_filter.types';
import { AppFeature } from 'state/features';
import { useStore } from 'state/useStore';
import { getVar } from 'utils/DOM';
interface ChatOpsConnectorsProps {
channelFilterId: ChannelFilter['id'];
@ -20,6 +19,7 @@ export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => {
const { channelFilterId, showLineNumber = true } = props;
const store = useStore();
const theme = useTheme2();
const { organizationStore, telegramChannelStore, msteamsChannelStore } = store;
const isSlackInstalled = Boolean(organizationStore.currentOrganization?.slack_team_identity);
@ -37,7 +37,7 @@ export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => {
}
return (
<Timeline.Item number={0} backgroundHexNumber={getVar('--tag-secondary')} isDisabled={!showLineNumber}>
<Timeline.Item number={0} backgroundHexNumber={theme.colors.secondary.main} isDisabled={!showLineNumber}>
<VerticalGroup>
{isSlackInstalled && <SlackConnector channelFilterId={channelFilterId} />}
{isTelegramInstalled && <TelegramConnector channelFilterId={channelFilterId} />}

View file

@ -2,10 +2,11 @@ import React, { ReactElement, useCallback, useEffect } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { LoadingPlaceholder, Select, useStyles2 } from '@grafana/ui';
import { LoadingPlaceholder, Select, useStyles2, useTheme2 } from '@grafana/ui';
import cn from 'classnames/bind';
import { get } from 'lodash-es';
import { observer } from 'mobx-react';
import { getLabelBackgroundTextColorObject } from 'styles/utils.styles';
import { EscalationPolicy, EscalationPolicyProps } from 'components/Policy/EscalationPolicy';
import { SortableList } from 'components/SortableList/SortableList';
@ -14,7 +15,6 @@ import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/W
import { EscalationChain } from 'models/escalation_chain/escalation_chain.types';
import { EscalationPolicyOption } from 'models/escalation_policy/escalation_policy.types';
import { useStore } from 'state/useStore';
import { getVar } from 'utils/DOM';
import { UserActions } from 'utils/authorization/authorization';
import styles from './EscalationChainSteps.module.css';
@ -41,6 +41,7 @@ export const EscalationChainSteps = observer((props: EscalationChainStepsProps)
const store = useStore();
const styles = useStyles2(getStyles);
const theme = useTheme2();
const { escalationPolicyStore } = store;
@ -74,6 +75,7 @@ export const EscalationChainSteps = observer((props: EscalationChainStepsProps)
const escalationPolicyIds = escalationPolicyStore.escalationChainToEscalationPolicy[id];
const isSlackInstalled = Boolean(store.organizationStore.currentOrganization?.slack_team_identity);
const { bgColor: successBgColor, textColor: successTextColor } = getLabelBackgroundTextColorObject('green', theme);
return (
// @ts-ignore
@ -124,8 +126,8 @@ export const EscalationChainSteps = observer((props: EscalationChainStepsProps)
{!isDisabled && (
<Timeline.Item
number={(escalationPolicyIds?.length || 0) + offset + 1}
backgroundHexNumber={isDisabled ? getVar('--tag-background-success') : getVar('--tag-secondary')}
textColor={isDisabled ? getVar('--tag-text-success') : undefined}
backgroundHexNumber={isDisabled ? successBgColor : theme.colors.secondary.main}
textColor={isDisabled ? successTextColor : undefined}
>
<WithPermissionControlTooltip userAction={UserActions.EscalationChainsWrite}>
<Select

View file

@ -32,7 +32,7 @@ export const IntegrationFormContainer = observer((props: IntegrationFormContaine
const [filterValue, setFilterValue] = useState('');
const [showNewIntegrationForm, setShowNewIntegrationForm] = useState(false);
const [selectedOption, setSelectedOption] = useState<ApiSchemas['AlertReceiveChannelIntegrationOptions']>(undefined);
const [showIntegrarionsListDrawer, setShowIntegrarionsListDrawer] = useState(id === 'new');
const [showIntegrationsListDrawer, setshowIntegrationsListDrawer] = useState(id === 'new');
const { alertReceiveChannelOptions } = alertReceiveChannelStore;
@ -56,7 +56,7 @@ export const IntegrationFormContainer = observer((props: IntegrationFormContaine
return (
<>
{showIntegrarionsListDrawer && (
{showIntegrationsListDrawer && (
<Drawer scrollableContent title="New Integration" onClose={onHide} closeOnMaskClick={false} width="640px">
<div className={cx('content')}>
<VerticalGroup>
@ -79,7 +79,7 @@ export const IntegrationFormContainer = observer((props: IntegrationFormContaine
</div>
</Drawer>
)}
{(showNewIntegrationForm || !showIntegrarionsListDrawer) && (
{(showNewIntegrationForm || !showIntegrationsListDrawer) && (
<Drawer scrollableContent title={getTitle()} onClose={onHide} closeOnMaskClick={false} width="640px">
<div className={cx('content')}>
<VerticalGroup>
@ -100,13 +100,13 @@ export const IntegrationFormContainer = observer((props: IntegrationFormContaine
function onBackClick(): void {
setShowNewIntegrationForm(false);
setShowIntegrarionsListDrawer(true);
setshowIntegrationsListDrawer(true);
}
function onBlockClick(option: ApiSchemas['AlertReceiveChannelIntegrationOptions']): void {
setSelectedOption(option);
setShowNewIntegrationForm(true);
setShowIntegrarionsListDrawer(false);
setshowIntegrationsListDrawer(false);
}
function getTitle(): string {

View file

@ -45,7 +45,7 @@ exports[`MobileAppConnection it shows a QR code if the app isn't already connect
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Download
</span>
@ -54,7 +54,7 @@ exports[`MobileAppConnection it shows a QR code if the app isn't already connect
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
The Grafana OnCall app is available on both the App Store and Google Play Store.
</span>
@ -84,7 +84,7 @@ exports[`MobileAppConnection it shows a QR code if the app isn't already connect
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
iOS
</span>
@ -109,7 +109,7 @@ exports[`MobileAppConnection it shows a QR code if the app isn't already connect
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
Android
</span>
@ -171,7 +171,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently disco
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Download
</span>
@ -180,7 +180,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently disco
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
The Grafana OnCall app is available on both the App Store and Google Play Store.
</span>
@ -210,7 +210,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently disco
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
iOS
</span>
@ -235,7 +235,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently disco
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
Android
</span>
@ -297,7 +297,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently fetch
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Download
</span>
@ -306,7 +306,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently fetch
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
The Grafana OnCall app is available on both the App Store and Google Play Store.
</span>
@ -336,7 +336,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently fetch
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
iOS
</span>
@ -361,7 +361,7 @@ exports[`MobileAppConnection it shows a loading message if it is currently fetch
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
Android
</span>
@ -388,7 +388,7 @@ exports[`MobileAppConnection it shows a warning when cloud is not connected 1`]
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
Please connect Grafana Cloud OnCall to use the mobile app
</span>
@ -438,7 +438,7 @@ exports[`MobileAppConnection it shows an error message if there was an error dis
class="css-1x53p5e css-1x53p5e--bordered css-1x53p5e--shadowed css-1x53p5e--withBackGround container__box"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
There was an error disconnecting your mobile app. Please try again.
</span>
@ -454,7 +454,7 @@ exports[`MobileAppConnection it shows an error message if there was an error dis
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Download
</span>
@ -463,7 +463,7 @@ exports[`MobileAppConnection it shows an error message if there was an error dis
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
The Grafana OnCall app is available on both the App Store and Google Play Store.
</span>
@ -493,7 +493,7 @@ exports[`MobileAppConnection it shows an error message if there was an error dis
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
iOS
</span>
@ -518,7 +518,7 @@ exports[`MobileAppConnection it shows an error message if there was an error dis
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
Android
</span>
@ -554,7 +554,7 @@ exports[`MobileAppConnection it shows an error message if there was an error fet
class="css-1x53p5e css-1x53p5e--bordered css-1x53p5e--shadowed css-1x53p5e--withBackGround container__box"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
There was an error fetching your QR code. Please try again.
</span>
@ -570,7 +570,7 @@ exports[`MobileAppConnection it shows an error message if there was an error fet
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Download
</span>
@ -579,7 +579,7 @@ exports[`MobileAppConnection it shows an error message if there was an error fet
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
The Grafana OnCall app is available on both the App Store and Google Play Store.
</span>
@ -609,7 +609,7 @@ exports[`MobileAppConnection it shows an error message if there was an error fet
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
iOS
</span>
@ -634,7 +634,7 @@ exports[`MobileAppConnection it shows an error message if there was an error fet
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
Android
</span>

View file

@ -10,7 +10,7 @@ exports[`DownloadIcons it renders properly 1`] = `
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Download
</span>
@ -19,7 +19,7 @@ exports[`DownloadIcons it renders properly 1`] = `
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
The Grafana OnCall app is available on both the App Store and Google Play Store.
</span>
@ -49,7 +49,7 @@ exports[`DownloadIcons it renders properly 1`] = `
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
iOS
</span>
@ -74,7 +74,7 @@ exports[`DownloadIcons it renders properly 1`] = `
src="[object Object]"
/>
<span
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium icon-text css-1287p17"
>
Android
</span>

View file

@ -10,7 +10,7 @@ exports[`LinkLoginButton it renders properly 1`] = `
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1287p17"
>
Sign in via deeplink
</span>
@ -19,7 +19,7 @@ exports[`LinkLoginButton it renders properly 1`] = `
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--primary css-77ouhj--medium css-1287p17"
>
Make sure to have the app installed
</span>

View file

@ -21,7 +21,7 @@ exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonDa
1. Launch the OnCall backend
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
Run hobby, dev or production backend. See
@ -31,7 +31,7 @@ exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonDa
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
here
</span>
@ -47,7 +47,7 @@ exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonDa
2. Let us know the base URL of your OnCall API
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
The OnCall backend must be reachable from your Grafana installation. Some examples are:
<br />
@ -117,7 +117,7 @@ exports[`PluginConfigPage If onCallApiUrl is set, and updatePluginStatus returns
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
ohhh nooo a plugin connection error
</span>
@ -169,7 +169,7 @@ exports[`PluginConfigPage It doesn't make any network calls if the plugin config
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
Connected to OnCall (v1.2.3, OpenSource)
</span>
@ -224,7 +224,7 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnec
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
Connected to OnCall (v1.2.3, OpenSource)
</span>
@ -279,7 +279,7 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnec
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
Connected to OnCall (v1.2.3, some-other-license)
</span>
@ -336,7 +336,7 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnec
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
ohhh noooo a sync issue
</span>
@ -391,7 +391,7 @@ exports[`PluginConfigPage Plugin reset: successful - false 1`] = `
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
There was an error resetting your plugin, try again.
</span>
@ -453,7 +453,7 @@ exports[`PluginConfigPage Plugin reset: successful - true 1`] = `
1. Launch the OnCall backend
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
Run hobby, dev or production backend. See
@ -463,7 +463,7 @@ exports[`PluginConfigPage Plugin reset: successful - true 1`] = `
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
here
</span>
@ -479,7 +479,7 @@ exports[`PluginConfigPage Plugin reset: successful - true 1`] = `
2. Let us know the base URL of your OnCall API
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
The OnCall backend must be reachable from your Grafana installation. Some examples are:
<br />

View file

@ -14,7 +14,7 @@ exports[`ConfigurationForm It doesn't allow the user to submit if the URL is inv
1. Launch the OnCall backend
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
Run hobby, dev or production backend. See
@ -24,7 +24,7 @@ exports[`ConfigurationForm It doesn't allow the user to submit if the URL is inv
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
here
</span>
@ -40,7 +40,7 @@ exports[`ConfigurationForm It doesn't allow the user to submit if the URL is inv
2. Let us know the base URL of your OnCall API
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
The OnCall backend must be reachable from your Grafana installation. Some examples are:
<br />
@ -125,7 +125,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
1. Launch the OnCall backend
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
Run hobby, dev or production backend. See
@ -135,7 +135,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
here
</span>
@ -151,7 +151,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
2. Let us know the base URL of your OnCall API
</p>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
The OnCall backend must be reachable from your Grafana installation. Some examples are:
<br />
@ -195,7 +195,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
</div>
<pre>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
ohhh nooo there was an error from the OnCall API
</span>
@ -204,7 +204,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
class="css-1x53p5e css-1x53p5e--withBackGround info-block"
>
<span
class="css-77ouhj--secondary css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--secondary css-77ouhj--medium css-1287p17"
>
Need help?
<br />
@ -216,7 +216,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
#grafana-oncall
</span>
@ -232,7 +232,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
here
</span>
@ -247,7 +247,7 @@ exports[`ConfigurationForm It shows an error message if the self hosted plugin A
target="_blank"
>
<span
class="css-77ouhj--link css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--link css-77ouhj--medium css-1287p17"
>
here
</span>

View file

@ -7,7 +7,7 @@ exports[`StatusMessageBlock It renders properly 1`] = `
data-testid="status-message-block"
>
<span
class="css-77ouhj--undefined css-77ouhj--medium css-1d6rs0l"
class="css-77ouhj--undefined css-77ouhj--medium css-1287p17"
>
helloooo
</span>

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { IconButton, VerticalGroup, HorizontalGroup, Field, Button } from '@grafana/ui';
import { IconButton, VerticalGroup, HorizontalGroup, Field, Button, useTheme2 } from '@grafana/ui';
import cn from 'classnames/bind';
import dayjs from 'dayjs';
import Draggable from 'react-draggable';
@ -15,7 +15,7 @@ import { Schedule, Shift } from 'models/schedule/schedule.types';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { getDateTime, getUTCString } from 'pages/schedule/Schedule.helpers';
import { useStore } from 'state/useStore';
import { getCoords, getVar, waitForElement } from 'utils/DOM';
import { getCoords, waitForElement } from 'utils/DOM';
import { GRAFANA_HEADER_HEIGHT } from 'utils/consts';
import { useDebouncedCallback } from 'utils/hooks';
@ -48,10 +48,11 @@ export const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
shiftId,
shiftStart: propsShiftStart = dayjs().startOf('day').add(1, 'day'),
shiftEnd: propsShiftEnd,
shiftColor = getVar('--tag-warning'),
shiftColor: shiftColorProp,
} = props;
const store = useStore();
const theme = useTheme2();
const [rotationName, setRotationName] = useState<string>(shiftId === 'new' ? 'Override' : 'Update override');
@ -63,6 +64,7 @@ export const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [errors, setErrors] = useState<{ [key: string]: string[] }>({});
const shiftColor = shiftColorProp || theme.colors.warning.main;
const updateShiftStart = useCallback(
(value) => {

View file

@ -1,7 +1,10 @@
import React, { useEffect } from 'react';
import { css } from '@emotion/css';
import { useStyles2 } from '@grafana/ui';
import cn from 'classnames/bind';
import dayjs from 'dayjs';
import { COLORS } from 'styles/utils.styles';
import { Text } from 'components/Text/Text';
import { WorkingHours } from 'components/WorkingHours/WorkingHours';
@ -23,6 +26,7 @@ const WEEK_IN_SECONDS = 60 * 60 * 24 * 7;
export const UserItem = ({ pk, shiftColor, shiftStart, shiftEnd }: UserItemProps) => {
const { userStore } = useStore();
const styles = useStyles2(getStyles);
useEffect(() => {
if (!userStore.items[pk]) {
@ -48,8 +52,16 @@ export const UserItem = ({ pk, shiftColor, shiftStart, shiftEnd }: UserItemProps
/>
)}
<div className={cx('user-title')}>
<Text strong>{name}</Text> <Text style={{ color: 'var(--always-gray)' }}>({desc})</Text>
<Text strong>{name}</Text> <Text className={styles.gray}>({desc})</Text>
</div>
</div>
);
};
const getStyles = () => {
return {
gray: css`
color: ${COLORS.ALWAYS_GREY};
`,
};
};

View file

@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import { Alert, Button, HorizontalGroup, InlineField, Input, VerticalGroup } from '@grafana/ui';
import { Alert, Button, HorizontalGroup, InlineField, Input, VerticalGroup, useTheme2 } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
@ -11,7 +11,6 @@ import { UserSettingsTab } from 'containers/UserSettings/UserSettings.types';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { AppFeature } from 'state/features';
import { useStore } from 'state/useStore';
import { getVar } from 'utils/DOM';
import styles from 'containers/UserSettings/parts/UserSettingsParts.module.css';
@ -26,6 +25,7 @@ export const PhoneConnector = observer((props: PhoneConnectorProps) => {
const { id, onTabChange } = props;
const store = useStore();
const theme = useTheme2();
const { userStore } = store;
const storeUser = userStore.items[id];
@ -134,8 +134,8 @@ export const PhoneConnector = observer((props: PhoneConnectorProps) => {
<VerticalGroup spacing="xs">
<div className={cx('tag-container')}>
<Tag
color={getVar('--tag-secondary-transparent')}
border={getVar('--border-weak')}
color={'rgba(204, 204, 220, 0.04)'}
border={theme.colors.border.weak}
className={cx('tag', 'tag-left')}
>
<Text type="primary" size="small">

View file

@ -1,69 +0,0 @@
.navbar-star-icon {
margin-right: 4px;
}
.header-topnavbar {
padding-top: 0;
padding-bottom: 0;
margin-bottom: 32px;
}
.navbar-heading {
padding: 4px;
border: 1px solid var(--gray-9);
width: initial;
font-size: 12px;
padding-top: 0;
margin-bottom: 0;
}
.navbar-link {
display: flex;
align-items: center;
padding-top: 6px;
}
.navbar-left {
display: flex;
flex-basis: 100%;
align-items: flex-start;
}
.navbar-heading-container {
display: flex;
flex-wrap: wrap;
align-items: center;
flex-direction: row;
column-gap: 8px;
row-gap: 8px;
margin-left: -50px;
}
.irm-icon {
font-size: 12px;
padding: 2px 4px;
border: 1px solid #ffb375;
color: #ffb375;
}
.banners {
margin-bottom: 24px;
&:empty {
padding-top: 0;
margin-bottom: 0;
}
}
.logo-container,
.page-header__img {
height: 32px;
}
.logo-container {
margin-top: 2px;
}
.page-header__title {
margin-bottom: 8px;
}

View file

@ -0,0 +1,76 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
export const getHeaderStyles = (theme: GrafanaTheme2) => {
return {
navbarStarIcon: css`
margin-right: 4px;
`,
headerTopNavbar: css`
padding-top: 0;
padding-bottom: 0;
margin-bottom: 32px;
`,
navbarHeading: css`
padding: 4px;
border: 1px solid ${theme.colors.secondary.border};
width: initial;
font-size: 12px;
padding-top: 0;
margin-bottom: 0;
`,
navbarLink: css`
display: flex;
align-items: center;
padding-top: 6px;
`,
navbarLeft: css`
display: flex;
flex-basis: 100%;
align-items: flex-start;
`,
navbarHeadingContainer: css`
display: flex;
flex-wrap: wrap;
align-items: center;
flex-direction: row;
column-gap: 8px;
row-gap: 8px;
margin-left: -50px;
`,
irmIcon: css`
font-size: 12px;
padding: 2px 4px;
border: 1px solid #ffb375;
color: #ffb375;
`,
banners: css`
margin-bottom: 24px;
&:empty {
padding-top: 0;
margin-bottom: 0;
}
`,
logoContainer: css`
height: 32px;
margin-top: 2px;
`,
pageHeaderImage: css`
height: 32px;
`,
pageHeaderTitle: css`
margin-bottom: 8px;
`,
};
};

View file

@ -1,7 +1,7 @@
import React from 'react';
import { Card, HorizontalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { cx } from '@emotion/css';
import { Card, HorizontalGroup, useStyles2 } from '@grafana/ui';
import { observer } from 'mobx-react';
import gitHubStarSVG from 'assets/img/github_star.svg';
@ -11,22 +11,21 @@ import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
import { useStore } from 'state/useStore';
import { APP_SUBTITLE } from 'utils/consts';
import styles from './Header.module.scss';
const cx = cn.bind(styles);
import { getHeaderStyles } from './Header.styles';
export const Header = observer(() => {
const store = useStore();
const styles = useStyles2(getHeaderStyles);
return (
<>
<div className={cx('root')}>
<div className={cx('page-header__inner', { 'header-topnavbar': isTopNavbar() })}>
<div className={cx('navbar-left')}>
<span className={cx('page-header__logo', 'logo-container')}>
<img className={cx('page-header__img')} src={logo} alt="Grafana OnCall" />
<div>
<div className={cx('page-header__inner', { [styles.headerTopNavbar]: isTopNavbar() })}>
<div className={cx(styles.navbarLeft)}>
<span className={cx('page-header__logo', styles.logoContainer)}>
<img className={cx(styles.pageHeaderImage)} src={logo} alt="Grafana OnCall" />
</span>
<div className="page-header__info-block">{renderHeading()}</div>
<div className={cx('page-header__info-block')}>{renderHeading()}</div>
</div>
</div>
</div>
@ -38,18 +37,18 @@ export const Header = observer(() => {
if (store.isOpenSource) {
return (
<div className={cx('heading')}>
<h1 className={cx('page-header__title')}>Grafana OnCall</h1>
<div className={cx('navbar-heading-container')}>
<h1 className={cx(styles.pageHeaderTitle)}>Grafana OnCall</h1>
<div className={cx(styles.navbarHeadingContainer)}>
<div className={cx('page-header__sub-title')}>{APP_SUBTITLE}</div>
<Card heading={undefined} className={cx('navbar-heading')}>
<Card heading={undefined} className={cx(styles.navbarHeading)}>
<a
href="https://github.com/grafana/oncall"
className={cx('navbar-link')}
className={cx(styles.navbarLink)}
target="_blank"
rel="noreferrer"
>
<img src={gitHubStarSVG} className={cx('navbar-star-icon')} alt="" /> Star us on GitHub
<img src={gitHubStarSVG} className={cx(styles.navbarStarIcon)} alt="" /> Star us on GitHub
</a>
</Card>
</div>
@ -60,7 +59,7 @@ export const Header = observer(() => {
return (
<>
<HorizontalGroup>
<h1 className={cx('page-header__title')}>Grafana OnCall</h1>
<h1 className={cx(styles.pageHeaderTitle)}>Grafana OnCall</h1>
</HorizontalGroup>
<div className={cx('page-header__sub-title')}>{APP_SUBTITLE}</div>
</>
@ -69,8 +68,9 @@ export const Header = observer(() => {
});
const Banners: React.FC = () => {
const styles = useStyles2(getHeaderStyles);
return (
<div className={cx('banners')}>
<div className={cx(styles.banners)}>
<Alerts />
</div>
);

View file

@ -5,7 +5,6 @@ import cn from 'classnames/bind';
import { Avatar } from 'components/Avatar/Avatar';
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';
@ -13,7 +12,6 @@ import { IncidentStatus } from 'models/alertgroup/alertgroup.types';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { SilenceButtonCascader } from 'pages/incidents/parts/SilenceButtonCascader';
import { move } from 'state/helpers';
import { getVar } from 'utils/DOM';
import { UserActions } from 'utils/authorization/authorization';
import { TEXT_ELLIPSIS_CLASS } from 'utils/consts';
@ -21,45 +19,6 @@ import styles from './Incident.module.scss';
const cx = cn.bind(styles);
export function getIncidentStatusTag(alert: ApiSchemas['AlertGroup']) {
switch (alert.status) {
case IncidentStatus.Firing:
return (
<Tag color={getVar('--tag-danger')} className={cx('status-tag')}>
<Text strong size="small">
Firing
</Text>
</Tag>
);
case IncidentStatus.Acknowledged:
return (
<Tag color={getVar('--tag-warning')} className={cx('status-tag')}>
<Text strong size="small">
Acknowledged
</Text>
</Tag>
);
case IncidentStatus.Resolved:
return (
<Tag color={getVar('--tag-primary')} className={cx('status-tag')}>
<Text strong size="small">
Resolved
</Text>
</Tag>
);
case IncidentStatus.Silenced:
return (
<Tag color={getVar('--tag-secondary')} className={cx('status-tag')}>
<Text strong size="small">
Silenced
</Text>
</Tag>
);
default:
return null;
}
}
export function renderRelatedUsers(incident: ApiSchemas['AlertGroup'], isFull = false) {
const { related_users } = incident;

View file

@ -1,51 +0,0 @@
.incident__tag {
padding: 5px 8px;
display: inline-flex;
align-items: center;
cursor: pointer;
}
.incident__icon {
margin-right: -4px;
margin-left: 2px;
}
.incident__options {
display: flex;
flex-direction: column;
}
.incident__option-item {
padding: 8px;
display: flex;
align-items: center;
flex-direction: row;
flex-shrink: 0;
white-space: nowrap;
border-left: 2px solid transparent;
cursor: pointer;
min-width: 84px;
display: flex;
gap: 8px;
flex-direction: row;
&:hover {
background: var(--gray-9);
}
&--acknowledge {
color: var(--tag-warning);
}
&--firing {
color: var(--error-text-color);
}
&--resolve {
color: var(--success-text-color);
}
}
.incident__option-span > div {
margin: 0;
}

View file

@ -0,0 +1,49 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
export const getIncidentDropdownStyles = (theme: GrafanaTheme2) => {
return {
incidentTag: css`
padding: 5px 8px;
display: inline-flex;
align-items: center;
cursor: pointer;
`,
incidentIcon: css`
margin-right: -4px;
margin-left: 2px;
`,
incidentOptions: css`
display: flex;
flex-direction: column;
`,
incidentOptionItem: css`
padding: 8px;
display: flex;
align-items: center;
flex-direction: row;
flex-shrink: 0;
white-space: nowrap;
border-left: 2px solid transparent;
cursor: pointer;
min-width: 84px;
display: flex;
gap: 8px;
flex-direction: row;
color: ${theme.colors.text.primary};
&:hover {
background: ${theme.colors.action.hover};
}
`,
incidentOptionEl: css`
> div {
margin: 0;
}
`,
};
};

View file

@ -1,33 +1,31 @@
import React, { FC, SyntheticEvent, useRef, useState } from 'react';
import { Icon, LoadingPlaceholder } from '@grafana/ui';
import cn from 'classnames/bind';
import { cx } from '@emotion/css';
import { Icon, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { getUtilStyles } from 'styles/utils.styles';
import { Tag } from 'components/Tag/Tag';
import { Tag, TagColor } from 'components/Tag/Tag';
import { Text } from 'components/Text/Text';
import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { AlertAction, IncidentStatus } from 'models/alertgroup/alertgroup.types';
import { ApiSchemas } from 'network/oncall-api/api.types';
import styles from 'pages/incidents/parts/IncidentDropdown.module.scss';
import { getVar } from 'utils/DOM';
import { UserActions } from 'utils/authorization/authorization';
import { getIncidentDropdownStyles } from './IncidentDropdown.styles';
import { SilenceSelect } from './SilenceSelect';
const cx = cn.bind(styles);
const getIncidentTagColor = (alert: ApiSchemas['AlertGroup']) => {
if (alert.status === IncidentStatus.Resolved) {
return getVar('--tag-primary');
return TagColor.SUCCESS;
}
if (alert.status === IncidentStatus.Firing) {
return getVar('--tag-danger');
return TagColor.ERROR;
}
if (alert.status === IncidentStatus.Acknowledged) {
return getVar('--tag-warning');
return TagColor.WARNING;
}
return getVar('--tag-secondary');
return TagColor.SECONDARY;
};
function IncidentStatusTag({
@ -37,12 +35,13 @@ function IncidentStatusTag({
alert: ApiSchemas['AlertGroup'];
openMenu: React.MouseEventHandler<HTMLElement>;
}) {
const styles = useStyles2(getIncidentDropdownStyles);
const forwardedRef = useRef<HTMLSpanElement>();
return (
<Tag
forwardedRef={forwardedRef}
className={cx('incident__tag')}
className={cx(styles.incidentTag)}
color={getIncidentTagColor(alert)}
onClick={() => {
const boundingRect = forwardedRef.current.getBoundingClientRect();
@ -50,10 +49,8 @@ function IncidentStatusTag({
openMenu({ pageX: boundingRect.left + LEFT_MARGIN, pageY: boundingRect.top + boundingRect.height } as any);
}}
>
<Text strong size="small">
{IncidentStatus[alert.status]}
</Text>
<Icon className={cx('incident__icon')} name="angle-down" size="sm" />
<Text size="small">{IncidentStatus[alert.status]}</Text>
<Icon className={cx(styles.incidentIcon)} name="angle-down" size="sm" />
</Tag>
);
}
@ -71,6 +68,9 @@ export const IncidentDropdown: FC<{
const [currentLoadingAction, setCurrentActionLoading] = useState<IncidentStatus>(undefined);
const [forcedOpenAction, setForcedOpenAction] = useState<string>(undefined);
const styles = useStyles2(getIncidentDropdownStyles);
const utilStyles = useStyles2(getUtilStyles);
const onClickFn = async (
ev: React.SyntheticEvent<HTMLDivElement>,
actionName: string,
@ -96,15 +96,15 @@ export const IncidentDropdown: FC<{
<WithContextMenu
forceIsOpen={forcedOpenAction === AlertAction.Resolve}
renderMenuItems={() => (
<div className={cx('incident__options', { 'u-disabled': isLoading })}>
<div className={cx(styles.incidentOptions, { [utilStyles.disabled]: isLoading })}>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--firing')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.Resolve, onUnresolve, IncidentStatus.Firing)}
>
Firing{' '}
{currentLoadingAction === IncidentStatus.Firing && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
@ -123,15 +123,15 @@ export const IncidentDropdown: FC<{
<WithContextMenu
forceIsOpen={forcedOpenAction === AlertAction.Acknowledge}
renderMenuItems={() => (
<div className={cx('incident__options', { 'u-disabled': isLoading })}>
<div className={cx(styles.incidentOptions, { [utilStyles.disabled]: isLoading })}>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--unacknowledge')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.Acknowledge, onUnacknowledge, IncidentStatus.Firing)}
>
Unacknowledge{' '}
{currentLoadingAction === IncidentStatus.Firing && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
@ -139,12 +139,12 @@ export const IncidentDropdown: FC<{
</WithPermissionControlTooltip>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--resolve')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.Acknowledge, onResolve, IncidentStatus.Resolved)}
>
Resolve{' '}
{currentLoadingAction === IncidentStatus.Resolved && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
@ -163,15 +163,15 @@ export const IncidentDropdown: FC<{
<WithContextMenu
forceIsOpen={forcedOpenAction === AlertAction.unResolve}
renderMenuItems={() => (
<div className={cx('incident__options', { 'u-disabled': isLoading })}>
<div className={cx(styles.incidentOptions, { [utilStyles.disabled]: isLoading })}>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--acknowledge')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.unResolve, onAcknowledge, IncidentStatus.Acknowledged)}
>
Acknowledge{' '}
{currentLoadingAction === IncidentStatus.Acknowledged && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
@ -179,19 +179,19 @@ export const IncidentDropdown: FC<{
</WithPermissionControlTooltip>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--resolve')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.unResolve, onResolve, IncidentStatus.Resolved)}
>
Resolve{' '}
{currentLoadingAction === IncidentStatus.Resolved && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
</div>
</WithPermissionControlTooltip>
<div className={cx('incident__option-item')}>
<div className={cx(styles.incidentOptionItem)}>
<SilenceSelect
placeholder={
currentLoadingAction === IncidentStatus.Silenced && isLoading ? 'Loading...' : 'Silence for'
@ -222,15 +222,15 @@ export const IncidentDropdown: FC<{
<WithContextMenu
forceIsOpen={forcedOpenAction === AlertAction.Silence}
renderMenuItems={() => (
<div className={cx('incident_options', { 'u-disabled': isLoading })}>
<div className={cx(styles.incidentOptions, { [utilStyles.disabled]: isLoading })}>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.Silence, onUnsilence, IncidentStatus.Firing)}
>
Unsilence{' '}
{currentLoadingAction === IncidentStatus.Firing && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
@ -238,12 +238,12 @@ export const IncidentDropdown: FC<{
</WithPermissionControlTooltip>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--acknowledge')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.Silence, onAcknowledge, IncidentStatus.Acknowledged)}
>
Acknowledge{' '}
{currentLoadingAction === IncidentStatus.Acknowledged && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}
@ -251,12 +251,12 @@ export const IncidentDropdown: FC<{
</WithPermissionControlTooltip>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<div
className={cx('incident__option-item', 'incident__option-item--resolve')}
className={cx(styles.incidentOptionItem)}
onClick={(e) => onClickFn(e, AlertAction.Silence, onAcknowledge, IncidentStatus.Resolved)}
>
Resolve{' '}
{currentLoadingAction === IncidentStatus.Resolved && isLoading && (
<span className={cx('incident__option-span')}>
<span className={cx(styles.incidentOptionEl)}>
<LoadingPlaceholder text="" />
</span>
)}

View file

@ -1,15 +1,20 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import tinycolor from 'tinycolor2';
export const getUtilStyles = (_theme: GrafanaTheme2) => {
export const getUtilStyles = (theme: GrafanaTheme2) => {
return {
width100: css`
width: 100%;
`,
disabled: css`
opacity: 0.5;
`,
thinLineBreak: css`
width: 100%;
border-top: 1px solid ${COLORS.ALWAYS_GREY};
border-top: 1px solid ${theme.colors.secondary.main};
margin-top: 8px;
opacity: 15%;
`,
@ -24,6 +29,34 @@ export const getUtilStyles = (_theme: GrafanaTheme2) => {
};
};
export function getLabelBackgroundTextColorObject(
color: string,
theme: GrafanaTheme2
): { bgColor: string; textColor: string; sourceColor: string } {
let sourceColor = theme.visualization.getColorByName(color);
let bgColor = '';
let textColor = '';
if (theme.isDark) {
bgColor = tinycolor(sourceColor).setAlpha(0.25).toString();
textColor = tinycolor(sourceColor).lighten(15).toString();
} else {
bgColor = tinycolor(sourceColor).setAlpha(0.25).toString();
textColor = tinycolor(sourceColor).darken(20).toString();
}
return { bgColor, textColor, sourceColor };
}
export function getLabelCss(color: string, theme: GrafanaTheme2) {
const { bgColor, textColor, sourceColor } = getLabelBackgroundTextColorObject(color, theme);
return css`
border: 1px solid ${sourceColor};
background-color: ${bgColor};
color: ${textColor};
`;
}
export const bem = (...args: string[]) =>
args.reduce((out, x, i) => {
out += x;
@ -39,5 +72,6 @@ export const bem = (...args: string[]) =>
export enum COLORS {
ALWAYS_GREY = '#ccccdc',
GRAY_8 = '#595959',
GRAY_9 = '#434343',
GREEN_5 = '#6ccf8e',
}

View file

@ -18,10 +18,6 @@ export const waitForElement = (selector: string) => {
});
};
export const getVar = (cssVar: string): string => {
return getComputedStyle(document.documentElement).getPropertyValue(cssVar);
};
export const getCoords = (elem) => {
// crossbrowser version
const box = elem.getBoundingClientRect();

View file

@ -13835,7 +13835,7 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinycolor2@1.6.0:
tinycolor2@1.6.0, tinycolor2@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==