mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Make two boxes in template form adaptative to the screen (#67967)
Make two boxes in template form adaptative to the screen width to be align in the same line when possible
This commit is contained in:
parent
e0e2535c96
commit
97802e44a6
@ -2,6 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { default as React, useState } from 'react';
|
import { default as React, useState } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
|
||||||
@ -28,6 +29,10 @@ jest.mock('@grafana/ui', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react-virtualized-auto-sizer', () => {
|
||||||
|
return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
const PayloadEditorWithState = () => {
|
const PayloadEditorWithState = () => {
|
||||||
const [payload, setPayload] = useState(DEFAULT_PAYLOAD);
|
const [payload, setPayload] = useState(DEFAULT_PAYLOAD);
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Badge, Button, CodeEditor, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
import { Badge, Button, CodeEditor, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
@ -86,10 +87,12 @@ export function PayloadEditor({
|
|||||||
<Icon name="info-circle" className={styles.tooltip} size="xl" />
|
<Icon name="info-circle" className={styles.tooltip} size="xl" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<AutoSizer disableHeight>
|
||||||
|
{({ width }) => (
|
||||||
|
<div className={styles.editorWrapper}>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
width={640}
|
width={width}
|
||||||
height={363}
|
height={362}
|
||||||
language={'json'}
|
language={'json'}
|
||||||
showLineNumbers={true}
|
showLineNumbers={true}
|
||||||
showMiniMap={false}
|
showMiniMap={false}
|
||||||
@ -97,6 +100,9 @@ export function PayloadEditor({
|
|||||||
readOnly={false}
|
readOnly={false}
|
||||||
onBlur={setPayload}
|
onBlur={setPayload}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
|
||||||
<div className={styles.buttonsWrapper}>
|
<div className={styles.buttonsWrapper}>
|
||||||
<Button
|
<Button
|
||||||
@ -159,28 +165,39 @@ const AlertTemplateDataTable = () => {
|
|||||||
};
|
};
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
jsonEditor: css`
|
jsonEditor: css`
|
||||||
width: 605px;
|
width: 100%;
|
||||||
height: 363px;
|
height: 100%;
|
||||||
`,
|
`,
|
||||||
buttonsWrapper: css`
|
buttonsWrapper: css`
|
||||||
margin-top: ${theme.spacing(1)};
|
margin-top: ${theme.spacing(1)};
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
`,
|
`,
|
||||||
button: css`
|
button: css`
|
||||||
flex: none;
|
flex: none;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
padding-right: ${theme.spacing(1)};
|
padding-right: ${theme.spacing(1)};
|
||||||
margin-right: ${theme.spacing(1)};
|
margin-right: ${theme.spacing(1)};
|
||||||
|
margin-bottom: ${theme.spacing(1)};
|
||||||
`,
|
`,
|
||||||
title: css`
|
title: css`
|
||||||
font-weight: ${theme.typography.fontWeightBold};
|
font-weight: ${theme.typography.fontWeightBold};
|
||||||
|
heigth: 41px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: ${theme.spacing(2)};
|
||||||
|
margin-top: 19px;
|
||||||
`,
|
`,
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
padding-top: 38px;
|
flex: 1;
|
||||||
|
min-width: 450px;
|
||||||
`,
|
`,
|
||||||
tooltip: css`
|
tooltip: css`
|
||||||
padding-left: ${theme.spacing(1)};
|
padding-left: ${theme.spacing(1)};
|
||||||
`,
|
`,
|
||||||
|
editorWrapper: css`
|
||||||
|
width: min-content;
|
||||||
|
padding-top: 7px;
|
||||||
|
`,
|
||||||
editor: css`
|
editor: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -4,6 +4,7 @@ import { Location } from 'history';
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm, useFormContext, Validate } from 'react-hook-form';
|
import { FormProvider, useForm, useFormContext, Validate } from 'react-hook-form';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Stack } from '@grafana/experimental';
|
||||||
@ -175,22 +176,25 @@ export const TemplateForm = ({ existing, alertManagerSourceName, config, provena
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<TemplatingGuideline />
|
<TemplatingGuideline />
|
||||||
<Stack direction="row" alignItems={'center'}>
|
<div className={styles.editorsWrapper}>
|
||||||
<div>
|
<div className={styles.contentContainer}>
|
||||||
<TabsBar>
|
<TabsBar>
|
||||||
<Tab label="Content" active={view === 'content'} onChangeTab={() => setView('content')} />
|
<Tab label="Content" active={view === 'content'} onChangeTab={() => setView('content')} />
|
||||||
{isGrafanaAlertManager && (
|
{isGrafanaAlertManager && (
|
||||||
<Tab label="Preview" active={view === 'preview'} onChangeTab={() => setView('preview')} />
|
<Tab label="Preview" active={view === 'preview'} onChangeTab={() => setView('preview')} />
|
||||||
)}
|
)}
|
||||||
</TabsBar>
|
</TabsBar>
|
||||||
<div className={styles.contentContainer}>
|
<div className={styles.contentContainerEditor}>
|
||||||
|
<AutoSizer>
|
||||||
|
{({ width }) => (
|
||||||
|
<>
|
||||||
{view === 'content' ? (
|
{view === 'content' ? (
|
||||||
<div>
|
<div>
|
||||||
<Field error={errors?.content?.message} invalid={!!errors.content?.message} required>
|
<Field error={errors?.content?.message} invalid={!!errors.content?.message} required>
|
||||||
<div className={styles.editWrapper}>
|
<div className={styles.editWrapper}>
|
||||||
<TemplateEditor
|
<TemplateEditor
|
||||||
value={getValues('content')}
|
value={getValues('content')}
|
||||||
width={640}
|
width={width}
|
||||||
height={363}
|
height={363}
|
||||||
onBlur={(value) => setValue('content', value)}
|
onBlur={(value) => setValue('content', value)}
|
||||||
/>
|
/>
|
||||||
@ -219,12 +223,16 @@ export const TemplateForm = ({ existing, alertManagerSourceName, config, provena
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={width}
|
||||||
payload={payload}
|
payload={payload}
|
||||||
templateName={watch('name')}
|
templateName={watch('name')}
|
||||||
setPayloadFormatError={setPayloadFormatError}
|
setPayloadFormatError={setPayloadFormatError}
|
||||||
payloadFormatError={payloadFormatError}
|
payloadFormatError={payloadFormatError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isGrafanaAlertManager && (
|
{isGrafanaAlertManager && (
|
||||||
@ -237,7 +245,7 @@ export const TemplateForm = ({ existing, alertManagerSourceName, config, provena
|
|||||||
onPayloadError={onPayloadError}
|
onPayloadError={onPayloadError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</div>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
<CollapsableSection label="Data cheat sheet" isOpen={false} className={styles.collapsableSection}>
|
<CollapsableSection label="Data cheat sheet" isOpen={false} className={styles.collapsableSection}>
|
||||||
<TemplateDataDocs />
|
<TemplateDataDocs />
|
||||||
@ -348,11 +356,13 @@ export function TemplatePreview({
|
|||||||
templateName,
|
templateName,
|
||||||
payloadFormatError,
|
payloadFormatError,
|
||||||
setPayloadFormatError,
|
setPayloadFormatError,
|
||||||
|
width,
|
||||||
}: {
|
}: {
|
||||||
payload: string;
|
payload: string;
|
||||||
templateName: string;
|
templateName: string;
|
||||||
payloadFormatError: string | null;
|
payloadFormatError: string | null;
|
||||||
setPayloadFormatError: (value: React.SetStateAction<string | null>) => void;
|
setPayloadFormatError: (value: React.SetStateAction<string | null>) => void;
|
||||||
|
width: number;
|
||||||
}) {
|
}) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
@ -378,8 +388,7 @@ export function TemplatePreview({
|
|||||||
useEffect(() => onPreview(), [onPreview]);
|
useEffect(() => onPreview(), [onPreview]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" alignItems="center" gap={2}>
|
<div style={{ width: `${width}px` }} className={styles.preview.wrapper}>
|
||||||
<Stack direction="column">
|
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<>
|
<>
|
||||||
<Spinner inline={true} /> Loading preview...
|
<Spinner inline={true} /> Loading preview...
|
||||||
@ -391,13 +400,17 @@ export function TemplatePreview({
|
|||||||
<Button onClick={onPreview} className={styles.preview.button} icon="arrow-up" type="button" variant="secondary">
|
<Button onClick={onPreview} className={styles.preview.button} icon="arrow-up" type="button" variant="secondary">
|
||||||
Refresh preview
|
Refresh preview
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</div>
|
||||||
</Stack>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
contentContainer: css`
|
contentContainer: css`
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: ${theme.spacing(6)};
|
||||||
|
`,
|
||||||
|
contentContainerEditor: css`
|
||||||
|
flex:1;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
gap: ${theme.spacing(2)};
|
gap: ${theme.spacing(2)};
|
||||||
@ -407,6 +420,8 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
${theme.breakpoints.up('xxl')} {
|
${theme.breakpoints.up('xxl')} {
|
||||||
flex - wrap: nowrap;
|
flex - wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
min-width: 450px;
|
||||||
|
height: 363px;
|
||||||
`,
|
`,
|
||||||
snippets: css`
|
snippets: css`
|
||||||
margin-top: ${theme.spacing(2)};
|
margin-top: ${theme.spacing(2)};
|
||||||
@ -417,6 +432,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
font-weight: ${theme.typography.fontWeightBold};
|
font-weight: ${theme.typography.fontWeightBold};
|
||||||
`,
|
`,
|
||||||
buttons: css`
|
buttons: css`
|
||||||
|
display: flex;
|
||||||
& > * + * {
|
& > * + * {
|
||||||
margin-left: ${theme.spacing(1)};
|
margin-left: ${theme.spacing(1)};
|
||||||
}
|
}
|
||||||
@ -426,43 +442,39 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
max-width: 758px;
|
max-width: 758px;
|
||||||
`,
|
`,
|
||||||
editWrapper: css`
|
editWrapper: css`
|
||||||
display: block;
|
display: flex;
|
||||||
|
width: 100%
|
||||||
|
heigth:100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 640px;
|
|
||||||
height: 363px;
|
|
||||||
`,
|
`,
|
||||||
toggle: css({
|
toggle: css`
|
||||||
color: theme.colors.text.secondary,
|
color: theme.colors.text.secondary,
|
||||||
marginRight: `${theme.spacing(1)}`,
|
marginRight: ${theme.spacing(1)}`,
|
||||||
}),
|
|
||||||
previewHeader: css({
|
|
||||||
display: 'flex',
|
|
||||||
cursor: 'pointer',
|
|
||||||
alignItems: 'baseline',
|
|
||||||
color: theme.colors.text.primary,
|
|
||||||
'&:hover': {
|
|
||||||
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
previewHeaderTitle: css({
|
|
||||||
flexGrow: 1,
|
|
||||||
overflow: 'hidden',
|
|
||||||
fontSize: theme.typography.h4.fontSize,
|
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
|
||||||
margin: 0,
|
|
||||||
}),
|
|
||||||
preview: {
|
preview: {
|
||||||
|
wrapper: css`
|
||||||
|
display: flex;
|
||||||
|
width: 100%
|
||||||
|
heigth:100%;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
`,
|
||||||
result: css`
|
result: css`
|
||||||
width: 640px;
|
width: 100%;
|
||||||
height: 363px;
|
height: 363px;
|
||||||
`,
|
`,
|
||||||
button: css`
|
button: css`
|
||||||
flex: none;
|
flex: none;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-top: ${theme.spacing(-3)};
|
margin-top: -6px;
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
collapsableSection: css`
|
collapsableSection: css`
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
`,
|
`,
|
||||||
|
editorsWrapper: css`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
|
@ -45,6 +45,7 @@ describe('TemplatePreview component', () => {
|
|||||||
it('Should render error if payload has wrong format', async () => {
|
it('Should render error if payload has wrong format', async () => {
|
||||||
render(
|
render(
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={50}
|
||||||
payload={'bla bla bla'}
|
payload={'bla bla bla'}
|
||||||
templateName="potato"
|
templateName="potato"
|
||||||
payloadFormatError={'Unexpected token b in JSON at position 0'}
|
payloadFormatError={'Unexpected token b in JSON at position 0'}
|
||||||
@ -61,6 +62,7 @@ describe('TemplatePreview component', () => {
|
|||||||
const setError = jest.fn();
|
const setError = jest.fn();
|
||||||
render(
|
render(
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={50}
|
||||||
payload={'{"a":"b"}'}
|
payload={'{"a":"b"}'}
|
||||||
templateName="potato"
|
templateName="potato"
|
||||||
payloadFormatError={'Unexpected token b in JSON at position 0'}
|
payloadFormatError={'Unexpected token b in JSON at position 0'}
|
||||||
@ -76,6 +78,7 @@ describe('TemplatePreview component', () => {
|
|||||||
it('Should render error if payload has wrong format rendering the preview', async () => {
|
it('Should render error if payload has wrong format rendering the preview', async () => {
|
||||||
render(
|
render(
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={50}
|
||||||
payload={'potatos and cherries'}
|
payload={'potatos and cherries'}
|
||||||
templateName="potato"
|
templateName="potato"
|
||||||
payloadFormatError={'Unexpected token b in JSON at position 0'}
|
payloadFormatError={'Unexpected token b in JSON at position 0'}
|
||||||
@ -95,6 +98,7 @@ describe('TemplatePreview component', () => {
|
|||||||
mockPreviewTemplateResponseRejected(server);
|
mockPreviewTemplateResponseRejected(server);
|
||||||
render(
|
render(
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={50}
|
||||||
payload={'[{"a":"b"}]'}
|
payload={'[{"a":"b"}]'}
|
||||||
templateName="potato"
|
templateName="potato"
|
||||||
payloadFormatError={null}
|
payloadFormatError={null}
|
||||||
@ -118,6 +122,7 @@ describe('TemplatePreview component', () => {
|
|||||||
mockPreviewTemplateResponse(server, response);
|
mockPreviewTemplateResponse(server, response);
|
||||||
render(
|
render(
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={50}
|
||||||
payload={'[{"a":"b"}]'}
|
payload={'[{"a":"b"}]'}
|
||||||
templateName="potato"
|
templateName="potato"
|
||||||
payloadFormatError={null}
|
payloadFormatError={null}
|
||||||
@ -143,6 +148,7 @@ describe('TemplatePreview component', () => {
|
|||||||
mockPreviewTemplateResponse(server, response);
|
mockPreviewTemplateResponse(server, response);
|
||||||
render(
|
render(
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
|
width={50}
|
||||||
payload={'[{"a":"b"}]'}
|
payload={'[{"a":"b"}]'}
|
||||||
templateName="potato"
|
templateName="potato"
|
||||||
payloadFormatError={null}
|
payloadFormatError={null}
|
||||||
|
Loading…
Reference in New Issue
Block a user