diff --git a/public/app/features/alerting/NextGenAlertingPage.tsx b/public/app/features/alerting/NextGenAlertingPage.tsx index 24e5694c6c1..547c9c6420b 100644 --- a/public/app/features/alerting/NextGenAlertingPage.tsx +++ b/public/app/features/alerting/NextGenAlertingPage.tsx @@ -3,8 +3,8 @@ import { hot } from 'react-hot-loader'; import { connect, ConnectedProps } from 'react-redux'; import { css } from 'emotion'; import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; import { PageToolbar, stylesFactory, ToolbarButton } from '@grafana/ui'; - import { config } from 'app/core/config'; import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; import { AlertingQueryEditor } from './components/AlertingQueryEditor'; @@ -97,7 +97,9 @@ class NextGenAlertingPageUnconnected extends PureComponent<Props> { } }; - onDiscard = () => {}; + onDiscard = () => { + locationService.replace(`${config.appSubUrl}/alerting/list`); + }; onTest = () => { const { alertDefinition, evaluateAlertDefinition, evaluateNotSavedAlertDefinition } = this.props; @@ -125,12 +127,12 @@ class NextGenAlertingPageUnconnected extends PureComponent<Props> { render() { const { alertDefinition, - getInstances, uiState, updateAlertDefinitionUiState, - queryRunner, getQueryOptions, + getInstances, onRunQueries, + queryRunner, } = this.props; const styles = getStyles(config.theme); @@ -146,10 +148,10 @@ class NextGenAlertingPageUnconnected extends PureComponent<Props> { leftPaneComponents={[ <AlertingQueryPreview key="queryPreview" - queryRunner={queryRunner!} // if the queryRunner is undefined here somethings very wrong so it's ok to throw an unhandled error - getInstances={getInstances} - queries={queryOptions.queries} onTest={this.onTest} + queries={queryOptions.queries} + getInstances={getInstances} + queryRunner={queryRunner!} onRunQueries={onRunQueries} />, <AlertingQueryEditor key="queryEditor" />, diff --git a/public/app/features/alerting/components/AlertingQueryPreview.tsx b/public/app/features/alerting/components/AlertingQueryPreview.tsx index bc947d88978..6a7e0537838 100644 --- a/public/app/features/alerting/components/AlertingQueryPreview.tsx +++ b/public/app/features/alerting/components/AlertingQueryPreview.tsx @@ -3,10 +3,12 @@ import { useObservable } from 'react-use'; import { css } from 'emotion'; import AutoSizer from 'react-virtualized-auto-sizer'; import { DataFrame, DataQuery, GrafanaTheme, PanelData } from '@grafana/data'; -import { Button, Icon, Tab, TabContent, TabsBar, useStyles } from '@grafana/ui'; -import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; +import { config } from '@grafana/runtime'; +import { Icon, Tab, TabContent, TabsBar } from '@grafana/ui'; import { PreviewQueryTab } from './PreviewQueryTab'; import { PreviewInstancesTab } from './PreviewInstancesTab'; +import { EmptyState } from './EmptyState'; +import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; enum Tabs { Query = 'query', @@ -26,10 +28,9 @@ interface Props { onRunQueries: () => void; } -export const AlertingQueryPreview: FC<Props> = ({ getInstances, onRunQueries, onTest, queryRunner, queries }) => { +export const AlertingQueryPreview: FC<Props> = ({ getInstances, onRunQueries, onTest, queries, queryRunner }) => { const [activeTab, setActiveTab] = useState<string>(Tabs.Query); - const styles = useStyles(getStyles); - + const styles = getStyles(config.theme); const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), []); const data = useObservable<PanelData>(observable); const instances = getInstances(); @@ -49,59 +50,66 @@ export const AlertingQueryPreview: FC<Props> = ({ getInstances, onRunQueries, on })} </TabsBar> <TabContent className={styles.tabContent}> - {data && data.state === 'Error' ? ( - <div className={styles.noQueries}> - <h4 className={styles.noQueriesHeader}>There was an error :(</h4> - <div>{data.error?.data?.error}</div> - </div> - ) : queries && queries.length > 0 ? ( - <AutoSizer style={{ width: '100%', height: '100%' }}> - {({ width, height }) => { - switch (activeTab) { - case Tabs.Instances: - return ( - <PreviewInstancesTab - isTested={instances.length > 0} - instances={instances} - styles={styles} - width={width} - height={height} - onTest={onTest} - /> - ); - - case Tabs.Query: - default: - if (data) { - return <PreviewQueryTab data={data} width={width} height={height} />; - } - return ( - <div className={styles.noQueries}> - <h4 className={styles.noQueriesHeader}>Run queries to view data.</h4> - <Button onClick={onRunQueries}>Run queries</Button> - </div> - ); - } - }} - </AutoSizer> - ) : ( - <div className={styles.noQueries}> - <h4 className={styles.noQueriesHeader}>No queries added.</h4> - <div>Start adding queries to this alert and a visualisation for your queries will appear here.</div> - <div> - Learn more about how to create alert definitions <Icon name="external-link-alt" /> - </div> - </div> - )} + {data && + (data.state === 'Error' ? ( + <EmptyState title="There was an error :("> + <div>{data.error?.data?.error}</div> + </EmptyState> + ) : ( + <QueriesAndInstances + instances={instances} + onTest={onTest} + data={data} + activeTab={activeTab} + onRunQueries={onRunQueries} + queries={queries} + /> + ))} </TabContent> </div> ); }; +interface PreviewProps { + queries: DataQuery[]; + instances: DataFrame[]; + onTest: () => void; + data: PanelData; + activeTab: string; + onRunQueries: () => void; +} + +const QueriesAndInstances: FC<PreviewProps> = ({ queries, instances, onTest, data, activeTab, onRunQueries }) => { + if (queries.length === 0) { + return ( + <EmptyState title="No queries added."> + <div>Start adding queries to this alert and a visualisation for your queries will appear here.</div> + <div> + Learn more about how to create alert definitions <Icon name="external-link-alt" /> + </div> + </EmptyState> + ); + } + + return ( + <AutoSizer style={{ width: '100%', height: '100%' }}> + {({ width, height }) => { + switch (activeTab) { + case Tabs.Instances: + return <PreviewInstancesTab instances={instances} width={width} height={height} onTest={onTest} />; + + case Tabs.Query: + default: + return <PreviewQueryTab data={data} width={width} height={height} onRunQueries={onRunQueries} />; + } + }} + </AutoSizer> + ); +}; + const getStyles = (theme: GrafanaTheme) => { return { wrapper: css` - label: alertDefinitionPreviewTabs; display: flex; flex-direction: column; width: 100%; @@ -112,19 +120,5 @@ const getStyles = (theme: GrafanaTheme) => { background: ${theme.colors.panelBg}; height: 100%; `, - noQueries: css` - color: ${theme.colors.textSemiWeak}; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - `, - noQueriesHeader: css` - color: ${theme.colors.textSemiWeak}; - `, }; }; - -export type PreviewStyles = ReturnType<typeof getStyles>; diff --git a/public/app/features/alerting/components/EmptyState.tsx b/public/app/features/alerting/components/EmptyState.tsx new file mode 100644 index 00000000000..fc043b4025a --- /dev/null +++ b/public/app/features/alerting/components/EmptyState.tsx @@ -0,0 +1,37 @@ +import React, { FC, ReactNode } from 'react'; +import { css } from 'emotion'; +import { useStyles } from '@grafana/ui'; +import { GrafanaTheme } from '@grafana/data'; + +interface Props { + title: string; + children: ReactNode | ReactNode[]; +} + +export const EmptyState: FC<Props> = ({ children, title }) => { + const styles = useStyles(getStyles); + + return ( + <div className={styles.emptyState}> + <h4 className={styles.emptyStateHeader}>{title}</h4> + {children} + </div> + ); +}; + +const getStyles = (theme: GrafanaTheme) => { + return { + emptyState: css` + color: ${theme.colors.textSemiWeak}; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + `, + emptyStateHeader: css` + color: ${theme.colors.textSemiWeak}; + `, + }; +}; diff --git a/public/app/features/alerting/components/PreviewInstancesTab.tsx b/public/app/features/alerting/components/PreviewInstancesTab.tsx index 438c1e8d3d6..32b9081b5b5 100644 --- a/public/app/features/alerting/components/PreviewInstancesTab.tsx +++ b/public/app/features/alerting/components/PreviewInstancesTab.tsx @@ -1,25 +1,22 @@ import React, { FC } from 'react'; import { DataFrame } from '@grafana/data'; import { Button, Table } from '@grafana/ui'; -import { PreviewStyles } from './AlertingQueryPreview'; +import { EmptyState } from './EmptyState'; interface Props { instances: DataFrame[]; - isTested: boolean; - styles: PreviewStyles; width: number; height: number; onTest: () => void; } -export const PreviewInstancesTab: FC<Props> = ({ instances, isTested, onTest, height, styles, width }) => { - if (!isTested) { +export const PreviewInstancesTab: FC<Props> = ({ instances, onTest, height, width }) => { + if (!instances.length) { return ( - <div className={styles.noQueries}> - <h4 className={styles.noQueriesHeader}>You haven’t tested your alert yet.</h4> + <EmptyState title="You haven’t tested your alert yet."> <div>In order to see your instances, you need to test your alert first.</div> <Button onClick={onTest}>Test alert now</Button> - </div> + </EmptyState> ); } return <Table data={instances[0]} height={height} width={width} />; diff --git a/public/app/features/alerting/components/PreviewQueryTab.tsx b/public/app/features/alerting/components/PreviewQueryTab.tsx index e9d3e4dad45..e19614bb472 100644 --- a/public/app/features/alerting/components/PreviewQueryTab.tsx +++ b/public/app/features/alerting/components/PreviewQueryTab.tsx @@ -1,15 +1,17 @@ import React, { FC, useMemo, useState } from 'react'; -import { getFrameDisplayName, GrafanaTheme, PanelData, SelectableValue, toDataFrame } from '@grafana/data'; -import { Select, stylesFactory, Table, useTheme } from '@grafana/ui'; import { css } from 'emotion'; +import { getFrameDisplayName, GrafanaTheme, PanelData, SelectableValue } from '@grafana/data'; +import { Button, Select, stylesFactory, Table, useTheme } from '@grafana/ui'; +import { EmptyState } from './EmptyState'; interface Props { - data?: PanelData; + data: PanelData; width: number; height: number; + onRunQueries: () => void; } -export const PreviewQueryTab: FC<Props> = ({ data, height, width }) => { +export const PreviewQueryTab: FC<Props> = ({ data, height, onRunQueries, width }) => { const [currentSeries, setSeries] = useState<number>(0); const theme = useTheme(); const styles = getStyles(theme, height); @@ -24,8 +26,16 @@ export const PreviewQueryTab: FC<Props> = ({ data, height, width }) => { // Select padding const padding = 16; - if (!data?.series?.length) { - return <Table data={toDataFrame([])} height={height} width={width} />; + if (!data) { + return ( + <EmptyState title="Run queries to view data."> + <Button onClick={onRunQueries}>Run queries</Button> + </EmptyState> + ); + } + + if (!data.series) { + return null; } if (data.series.length > 1) { @@ -48,12 +58,14 @@ export const PreviewQueryTab: FC<Props> = ({ data, height, width }) => { </div> ); } + return <Table data={data.series[0]} height={height} width={width} />; }; const getStyles = stylesFactory((theme: GrafanaTheme, height: number) => { return { wrapper: css` + label: preview-wrapper; height: ${height}px; `, selectWrapper: css`