Skip to content

Commit 0a5c272

Browse files
authored
Merge pull request #1539 from github/koesie10/alert-components
Add Alert component for showing warnings and errors
2 parents b0dab96 + 43bcd69 commit 0a5c272

4 files changed

Lines changed: 212 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from 'react';
2+
3+
import { ComponentStory, ComponentMeta } from '@storybook/react';
4+
import { VSCodeButton, VSCodeLink } from '@vscode/webview-ui-toolkit/react';
5+
6+
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
7+
import { Alert } from '../../view/common';
8+
9+
export default {
10+
title: 'Alert',
11+
component: Alert,
12+
decorators: [
13+
(Story) => (
14+
<VariantAnalysisContainer>
15+
<Story />
16+
</VariantAnalysisContainer>
17+
)
18+
],
19+
} as ComponentMeta<typeof Alert>;
20+
21+
const Template: ComponentStory<typeof Alert> = (args) => (
22+
<Alert {...args} />
23+
);
24+
25+
export const Warning = Template.bind({});
26+
Warning.args = {
27+
type: 'warning',
28+
title: 'This query found a warning',
29+
message: <>Warning content with <VSCodeLink>links</VSCodeLink></>,
30+
};
31+
32+
export const WarningInverse = Template.bind({});
33+
WarningInverse.args = {
34+
...Warning.args,
35+
message: 'Warning content',
36+
inverse: true,
37+
};
38+
39+
export const WarningExample = Template.bind({});
40+
WarningExample.args = {
41+
type: 'warning',
42+
title: 'Query manually stopped',
43+
message: 'This query was manually stopped before the analysis completed. Results may be partial.',
44+
};
45+
46+
export const Error = Template.bind({});
47+
Error.args = {
48+
type: 'error',
49+
title: 'This query found an error',
50+
message: <>Error content with <VSCodeLink>links</VSCodeLink></>,
51+
};
52+
53+
export const ErrorInverse = Template.bind({});
54+
ErrorInverse.args = {
55+
...Error.args,
56+
message: 'Error content',
57+
inverse: true,
58+
};
59+
60+
export const ErrorExample = Template.bind({});
61+
ErrorExample.args = {
62+
type: 'error',
63+
title: 'Request failed',
64+
message: <>
65+
Request to https://api.github.com/repos/octodemo/Hello-World/code-scanning/codeql/queries failed.{' '}
66+
<VSCodeLink>Check logs</VSCodeLink> and try running this query again.
67+
</>,
68+
};
69+
70+
export const ErrorWithButtons = Template.bind({});
71+
ErrorWithButtons.args = {
72+
type: 'error',
73+
title: 'Request failed',
74+
message: 'Request to https://api.github.com/repos/octodemo/Hello-World/code-scanning/codeql/queries failed. Try running this query again.',
75+
actions: (
76+
<>
77+
<VSCodeButton appearance="secondary">View logs</VSCodeButton>
78+
<VSCodeButton>Retry</VSCodeButton>
79+
</>
80+
),
81+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import * as React from 'react';
2+
import { ReactNode } from 'react';
3+
import styled from 'styled-components';
4+
5+
type ContainerProps = {
6+
type: 'warning' | 'error';
7+
inverse?: boolean;
8+
};
9+
10+
const getBackgroundColor = ({ type, inverse }: ContainerProps): string => {
11+
if (!inverse) {
12+
return 'var(--vscode-notifications-background)';
13+
}
14+
15+
switch (type) {
16+
case 'warning':
17+
return 'var(--vscode-editorWarning-foreground)';
18+
case 'error':
19+
return 'var(--vscode-debugExceptionWidget-border)';
20+
}
21+
};
22+
23+
const getTextColor = ({ type, inverse }: ContainerProps): string => {
24+
if (!inverse) {
25+
return 'var(--vscode-editor-foreground)';
26+
}
27+
28+
switch (type) {
29+
case 'warning':
30+
return 'var(--vscode-editor-background)';
31+
case 'error':
32+
return 'var(--vscode-list-activeSelectionForeground)';
33+
}
34+
};
35+
36+
const getBorderColor = ({ type }: ContainerProps): string => {
37+
switch (type) {
38+
case 'warning':
39+
return 'var(--vscode-editorWarning-foreground)';
40+
case 'error':
41+
return 'var(--vscode-editorError-foreground)';
42+
}
43+
};
44+
45+
const getTypeText = (type: ContainerProps['type']): string => {
46+
switch (type) {
47+
case 'warning':
48+
return 'Warning';
49+
case 'error':
50+
return 'Error';
51+
}
52+
};
53+
54+
const Container = styled.div<ContainerProps>`
55+
display: flex;
56+
flex-direction: column;
57+
align-items: flex-start;
58+
gap: 1em;
59+
padding: 1em;
60+
61+
color: ${props => getTextColor(props)};
62+
background-color: ${props => getBackgroundColor(props)};
63+
border: 1px solid ${props => getBorderColor(props)};
64+
`;
65+
66+
const Title = styled.div`
67+
font-size: 0.85em;
68+
font-weight: 800;
69+
text-transform: uppercase;
70+
`;
71+
72+
const ActionsContainer = styled.div`
73+
display: flex;
74+
flex-direction: row;
75+
gap: 0.75em;
76+
margin-left: auto;
77+
`;
78+
79+
type Props = {
80+
type: 'warning' | 'error';
81+
title: string;
82+
message: ReactNode;
83+
84+
actions?: ReactNode;
85+
86+
// Inverse the color scheme
87+
inverse?: boolean;
88+
};
89+
90+
export const Alert = ({ type, title, message, actions, inverse }: Props) => {
91+
return (
92+
<Container type={type} inverse={inverse}>
93+
<Title>{getTypeText(type)}: {title}</Title>
94+
<span>{message}</span>
95+
{actions && <ActionsContainer>{actions}</ActionsContainer>}
96+
</Container>
97+
);
98+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { Alert } from '../Alert';
4+
5+
describe(Alert.name, () => {
6+
it('renders a warning correctly', () => {
7+
render(<Alert type="warning" title="Warning title" message="Warning content" />);
8+
9+
expect(screen.getByText('Warning: Warning title')).toBeInTheDocument();
10+
expect(screen.getByText('Warning content')).toBeInTheDocument();
11+
});
12+
13+
it('renders an error correctly', () => {
14+
render(<Alert type="error" title="Error title" message="Error content" />);
15+
16+
expect(screen.getByText('Error: Error title')).toBeInTheDocument();
17+
expect(screen.getByText('Error content')).toBeInTheDocument();
18+
});
19+
20+
it('renders actions correctly', () => {
21+
render(
22+
<Alert
23+
type="error"
24+
title="Error title"
25+
message="Error content"
26+
actions={<>My actions content</>}
27+
/>
28+
);
29+
30+
expect(screen.getByText('My actions content')).toBeInTheDocument();
31+
});
32+
});

extensions/ql-vscode/src/view/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './icon';
2+
export * from './Alert';
23
export * from './CodePaths';
34
export * from './FileCodeSnippet';
45
export * from './HorizontalSpace';

0 commit comments

Comments
 (0)