Skip to content

Commit

Permalink
Redesign check type selection screen (#957)
Browse files Browse the repository at this point in the history
* feat: redesign check type selection screen

* feat: add new status badge

* fix: change check group icon color

* fix: lint

* feat: add new icons to check types

This creates a dependency to @grafana/data 11.3.0 as they've been recently added there.

* chore: update grafana version dependency

* chore: update yarn lockfile

* fix: update files after merge with main

* fix: update after merge with main

* fix: use old icons until grafana 11.4.0 is published

* fix: tests

* fix: prevent showing 3 columns of check cards

* fix: improve styling and breakpoint transitions

* fix: show newStatusBadge on check's form

* fix: change tests not to use data-test-id

* fix: positioning of Toggletip in check's form

- workaround to fix the bug in floating-ui when setting containerType: inline-size
- more info here floating-ui/floating-ui#3067

* fix: address review comments

* fix: address review comments

* fix: add container queries
  • Loading branch information
VikaCep authored Nov 11, 2024
1 parent 60b2a78 commit e3c91c1
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 265 deletions.
77 changes: 0 additions & 77 deletions src/components/CheckEditor/FormComponents/CheckStatusBadge.tsx

This file was deleted.

60 changes: 60 additions & 0 deletions src/components/CheckEditor/FormComponents/CheckStatusInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Badge, Icon, Stack, TextLink, useStyles2 } from '@grafana/ui';
import { css } from '@emotion/css';

import { CheckStatus } from 'types';
import { Toggletip } from 'components/Toggletip';

export interface CheckStatusInfoProps {
description?: string;
docsLink?: string;
value?: CheckStatus;
}

export const CheckStatusInfo = ({ description, docsLink }: CheckStatusInfoProps) => {
const styles = useStyles2((theme: GrafanaTheme2) => getStyles(theme));

return (
<Stack>
<Toggletip
content={
<div>
{description}
{docsLink && (
<>
{` `}
<TextLink href={docsLink} external variant="bodySmall">
Read more
</TextLink>
</>
)}
</div>
}
>
<button className={styles.infoLink} type="button">
<Icon name={`info-circle`} size={'lg'} className={styles.infoIcon} />
</button>
</Toggletip>
</Stack>
);
};

export const NewStatusBadge = ({ status, className }: { status: CheckStatus; className?: string }) => {
if (![CheckStatus.EXPERIMENTAL, CheckStatus.PRIVATE_PREVIEW, CheckStatus.PUBLIC_PREVIEW].includes(status)) {
return null;
}

return <Badge text={'NEW'} color={'orange'} className={className} />;
};

const getStyles = (theme: GrafanaTheme2) => ({
infoLink: css({
background: `none`,
border: `none`,
padding: 0,
}),
infoIcon: css({
color: theme.colors.info.main,
}),
});
1 change: 1 addition & 0 deletions src/components/CheckForm/FormLayout/FormLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ const getStyles = (theme: GrafanaTheme2) => {
containerName,
containerType: `inline-size`,
height: '100%',
contain: 'layout',
}),
container: css({
display: 'grid',
Expand Down
28 changes: 22 additions & 6 deletions src/components/CheckForm/FormLayout/FormSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { Box, Stack, Text, useStyles2 } from '@grafana/ui';
import { css } from '@emotion/css';

import { CheckFormValues } from 'types';
import { CheckStatusBadge, CheckStatusBadgeProps } from 'components/CheckEditor/FormComponents/CheckStatusBadge';
import {
CheckStatusInfo,
CheckStatusInfoProps,
NewStatusBadge,
} from 'components/CheckEditor/FormComponents/CheckStatusInfo';

import { FORM_MAX_WIDTH } from './FormLayout';

Expand All @@ -15,7 +19,7 @@ export type FormSectionProps = {
label: string;
fields?: Array<FieldPath<CheckFormValues>>;
index: number;
status?: CheckStatusBadgeProps;
status?: CheckStatusInfoProps;
};

// return doesn't matter as we take over how this behaves internally
Expand All @@ -32,13 +36,16 @@ export const FormSectionInternal = ({ activeSection, children, label, index, sta
}

return (
<div data-fs-element={`Form section ${label}`}>
<div data-fs-element={`Form section ${label}`} className={styles.formContainer}>
<Box marginBottom={4}>
<Text element="h2" variant="h3">
<Stack gap={2}>
<div className={styles.header}>
{`${index + 1}. ${label}`}
{status && <CheckStatusBadge {...status} />}
</Stack>
<Stack gap={1}>
{status?.value && <NewStatusBadge status={status.value} />}
{status && <CheckStatusInfo {...status} />}
</Stack>
</div>
</Text>
</Box>
<div className={styles.sectionContent}>{children}</div>
Expand All @@ -48,8 +55,17 @@ export const FormSectionInternal = ({ activeSection, children, label, index, sta

const getStyles = (theme: GrafanaTheme2) => {
return {
header: css({
position: 'relative',
display: 'flex',
gap: theme.spacing(2),
}),
sectionContent: css({
maxWidth: FORM_MAX_WIDTH,
}),

formContainer: css({
position: 'relative',
}),
};
};
77 changes: 58 additions & 19 deletions src/components/ChooseCheckGroup/CheckGroupCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { CheckTypeGroup, ROUTES } from 'types';
import { CheckTypeGroupOption } from 'hooks/useCheckTypeGroupOptions';
import { useCheckTypeOptions } from 'hooks/useCheckTypeOptions';
import { useLimits } from 'hooks/useLimits';
import { CheckStatusInfo, NewStatusBadge } from 'components/CheckEditor/FormComponents/CheckStatusInfo';
import { getRoute } from 'components/Routing.utils';

import { Card } from '../Card';
import { CheckStatusBadge } from '../CheckEditor/FormComponents/CheckStatusBadge';
import { Protocol } from './Protocol';

export const CheckGroupCard = ({ group }: { group: CheckTypeGroupOption }) => {
Expand All @@ -26,38 +26,41 @@ export const CheckGroupCard = ({ group }: { group: CheckTypeGroupOption }) => {
const disabled = Boolean(tooltip);

return (
<Card key={group.label} data-testid={`${DataTestIds.CHECK_GROUP_CARD}-${group.value}`}>
<Card key={group.label} data-testid={`${DataTestIds.CHECK_GROUP_CARD}-${group.value}`} className={styles.checkCard}>
<Stack direction={`column`} justifyContent={`center`} gap={2}>
<Stack justifyContent={`center`}>
<Icon name={group.icon} size="xxxl" />
{shouldShowStatus && checksWithStatus[0].status && (
<NewStatusBadge status={checksWithStatus[0].status.value} className={styles.newBadge} />
)}
</Stack>
<Card.Heading variant="h6">
<div>{group.label} </div>
<Card.Heading variant="h5">
<Stack justifyContent={'center'}>
<div className={styles.groupName}>{group.label}</div>
{shouldShowStatus && checksWithStatus[0].status && <CheckStatusInfo {...checksWithStatus[0].status} />}
</Stack>
</Card.Heading>
<div className={styles.desc}>{group.description}</div>
<div>
<div>{group.description}</div>
<div className={styles.cardButton}>
<LinkButton
icon={!isReady ? 'fa fa-spinner' : undefined}
disabled={disabled}
href={`${getRoute(ROUTES.NewCheck)}/${group.value}`}
tooltip={getTooltip(limits, group.value)}
>
{group.label} check
{group.label}
</LinkButton>
</div>
<div className={styles.protocols}>
<Stack direction={`column`} gap={2}>
Supported protocols:
<Stack justifyContent={`center`}>
{group.protocols.map((protocol) => {
return <Protocol key={protocol.label} {...protocol} href={disabled ? undefined : protocol.href} />;
})}
</Stack>
{shouldShowStatus && checksWithStatus[0].status && (
<Stack justifyContent={`center`}>
<CheckStatusBadge {...checksWithStatus[0].status} />
</Stack>
)}
<div className={styles.cardFooter}>
{group.protocols.map((protocol, index) => (
<span key={protocol.label}>
<Protocol {...protocol} href={disabled ? undefined : protocol.href} />
{index < group.protocols.length - 1 && ', '}
</span>
))}
</div>
</Stack>
</div>
</Stack>
Expand Down Expand Up @@ -99,10 +102,46 @@ function getTooltip(
}

const getStyles = (theme: GrafanaTheme2) => ({
newBadge: css({
position: 'absolute',
right: 0,
marginRight: theme.spacing(3),
marginTop: theme.spacing(2),
height: '26px',
}),

cardButton: css({
marginTop: 'auto',
}),

cardFooter: css({
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: '4px',
marginTop: theme.spacing(1),
}),

checkCard: css({
minWidth: '0',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'wrap',

'> div:first-of-type': {
height: '100%',
},
}),
desc: css({
color: theme.colors.text.secondary,
}),
groupName: css({
color: theme.colors.text.primary,
}),
protocols: css({
marginTop: theme.spacing(1),
borderTop: `1px solid ${theme.colors.border.weak}`,
color: theme.colors.text.primary,
display: 'flex',
justifyContent: 'center',
}),
});
20 changes: 10 additions & 10 deletions src/components/ChooseCheckGroup/ChooseCheckGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ async function renderChooseCheckGroup({ checkLimit = 10, scriptedLimit = 10 } =
it('shows check type options correctly with feature flags off', async () => {
await renderChooseCheckGroup();

expect(screen.getByText('API Endpoint')).toBeInTheDocument();
expect(screen.getByText('Multi Step')).toBeInTheDocument();
expect(screen.queryByText('Scripted')).not.toBeInTheDocument();
expect(screen.queryByText('Browser')).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: `API Endpoint` })).toBeInTheDocument();
expect(screen.queryByRole('link', { name: `Multi Step` })).toBeInTheDocument();
expect(screen.queryByRole('link', { name: `Scripted` })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: `Browser` })).not.toBeInTheDocument();
});

it('shows the scripted card with correct feature flag on', async () => {
Expand All @@ -47,7 +47,7 @@ it('shows the scripted card with correct feature flag on', async () => {
});

await renderChooseCheckGroup();
expect(screen.getByText('Scripted')).toBeInTheDocument();
expect(screen.getByRole('link', { name: `Scripted` })).toBeInTheDocument();
});

it('shows the browser card with correct feature flag on', async () => {
Expand All @@ -57,7 +57,7 @@ it('shows the browser card with correct feature flag on', async () => {
});

await renderChooseCheckGroup();
expect(screen.getByText('Browser')).toBeInTheDocument();
expect(screen.getByRole('link', { name: `Browser` })).toBeInTheDocument();
});

it(`doesn't show gRPC option by default`, async () => {
Expand Down Expand Up @@ -94,10 +94,10 @@ it(`shows an error alert when user is HG Free user with over 100k execution limi
const alert = await screen.findByText(/You have reached your monthly execution limit of/);
expect(alert).toBeInTheDocument();

const apiEndPointButton = screen.getByRole(`link`, { name: `API Endpoint check` });
const multiStepButton = screen.getByRole(`link`, { name: `Multi Step check` });
const scriptedButton = screen.getByRole(`link`, { name: `Scripted check` });
const browserButton = screen.getByRole(`link`, { name: `Browser check` });
const apiEndPointButton = screen.getByRole('link', { name: `API Endpoint` });
const multiStepButton = screen.getByRole('link', { name: `Multi Step` });
const scriptedButton = screen.getByRole('link', { name: `Scripted` });
const browserButton = screen.getByRole('link', { name: `Browser` });

expect(apiEndPointButton).toHaveAttribute(`aria-disabled`, `true`);
expect(multiStepButton).toHaveAttribute(`aria-disabled`, `true`);
Expand Down
Loading

0 comments on commit e3c91c1

Please sign in to comment.