mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 02:23:28 +08:00
NgAlerting: View query result (#30218)
* Fix query preview add tabs to options * break out tabs to components * add refresh button * minor things after PR review * hide queries * Add simple error screen if there's an error * dropdown with different frames * move onrunqueries to redux * cleanup * show actual error
This commit is contained in:
@ -16,6 +16,7 @@ export const getNextRefIdChar = (queries: DataQuery[]): string => {
|
||||
export function addQuery(queries: DataQuery[], query?: Partial<DataQuery>): DataQuery[] {
|
||||
const q = query || {};
|
||||
q.refId = getNextRefIdChar(queries);
|
||||
q.hide = false;
|
||||
return [...queries, q as DataQuery];
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import React, { FC, FormEvent } from 'react';
|
||||
import React, { FC, FormEvent, useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { Field, Input, Select, TextArea, useStyles } from '@grafana/ui';
|
||||
import { Field, Input, Tab, TabContent, TabsBar, TextArea, useStyles } from '@grafana/ui';
|
||||
import { AlertDefinition, NotificationChannelType } from 'app/types';
|
||||
import { mapChannelsToSelectableValue } from '../utils/notificationChannels';
|
||||
|
||||
interface Props {
|
||||
alertDefinition: AlertDefinition;
|
||||
@ -11,45 +10,70 @@ interface Props {
|
||||
onChange: (event: FormEvent) => void;
|
||||
}
|
||||
|
||||
export const AlertDefinitionOptions: FC<Props> = ({ alertDefinition, notificationChannelTypes, onChange }) => {
|
||||
enum Tabs {
|
||||
Alert = 'alert',
|
||||
Panel = 'panel',
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: Tabs.Alert, text: 'Alert definition' },
|
||||
{ id: Tabs.Panel, text: 'Panel' },
|
||||
];
|
||||
|
||||
export const AlertDefinitionOptions: FC<Props> = ({ alertDefinition, onChange }) => {
|
||||
const styles = useStyles(getStyles);
|
||||
const [activeTab, setActiveTab] = useState<string>(Tabs.Alert);
|
||||
|
||||
return (
|
||||
<div style={{ paddingTop: '16px' }}>
|
||||
<div className={styles.container}>
|
||||
<h4>Alert definition</h4>
|
||||
<Field label="Name">
|
||||
<Input width={25} name="name" value={alertDefinition.name} onChange={onChange} />
|
||||
</Field>
|
||||
<Field label="Description" description="What does the alert do and why was it created">
|
||||
<TextArea rows={5} width={25} name="description" value={alertDefinition.description} onChange={onChange} />
|
||||
</Field>
|
||||
<Field label="Evaluate">
|
||||
<span>Every For</span>
|
||||
</Field>
|
||||
<Field label="Conditions">
|
||||
<div></div>
|
||||
</Field>
|
||||
{notificationChannelTypes.length > 0 && (
|
||||
<>
|
||||
<Field label="Notification channel">
|
||||
<Select options={mapChannelsToSelectableValue(notificationChannelTypes, false)} onChange={onChange} />
|
||||
<div className={styles.container}>
|
||||
<TabsBar>
|
||||
{tabs.map((tab, index) => (
|
||||
<Tab
|
||||
key={`${tab.id}-${index}`}
|
||||
label={tab.text}
|
||||
active={tab.id === activeTab}
|
||||
onChangeTab={() => setActiveTab(tab.id)}
|
||||
/>
|
||||
))}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
{activeTab === Tabs.Alert && (
|
||||
<div>
|
||||
<Field label="Name">
|
||||
<Input width={25} name="name" value={alertDefinition.name} onChange={onChange} />
|
||||
</Field>
|
||||
</>
|
||||
<Field label="Description" description="What does the alert do and why was it created">
|
||||
<TextArea
|
||||
rows={5}
|
||||
width={25}
|
||||
name="description"
|
||||
value={alertDefinition.description}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Evaluate">
|
||||
<span>Every For</span>
|
||||
</Field>
|
||||
<Field label="Conditions">
|
||||
<div></div>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{activeTab === Tabs.Panel && <div>VizPicker</div>}
|
||||
</TabContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
padding-top: ${theme.spacing.md};
|
||||
`,
|
||||
container: css`
|
||||
padding: ${theme.spacing.md};
|
||||
background-color: ${theme.colors.panelBg};
|
||||
margin-top: ${theme.spacing.md};
|
||||
height: 100%;
|
||||
`,
|
||||
tabContent: css`
|
||||
background: ${theme.colors.panelBg};
|
||||
height: 100%;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@ -1,14 +1,13 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { css } from 'emotion';
|
||||
import { dateMath, GrafanaTheme } from '@grafana/data';
|
||||
import { stylesFactory } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { RefreshPicker, stylesFactory } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { QueryGroup } from '../../query/components/QueryGroup';
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { QueryGroupOptions } from '../../query/components/QueryGroupOptions';
|
||||
import { queryOptionsChange } from '../state/actions';
|
||||
import { StoreState } from '../../../types';
|
||||
import { onRunQueries, queryOptionsChange } from '../state/actions';
|
||||
import { QueryGroupOptions, StoreState } from 'app/types';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@ -18,6 +17,7 @@ interface ConnectedProps {
|
||||
}
|
||||
interface DispatchProps {
|
||||
queryOptionsChange: typeof queryOptionsChange;
|
||||
onRunQueries: typeof onRunQueries;
|
||||
}
|
||||
|
||||
type Props = ConnectedProps & DispatchProps & OwnProps;
|
||||
@ -28,17 +28,11 @@ export class AlertingQueryEditor extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
onRunQueries = () => {
|
||||
const { queryRunner, queryOptions } = this.props;
|
||||
const timeRange = { from: 'now-1h', to: 'now' };
|
||||
this.props.onRunQueries();
|
||||
};
|
||||
|
||||
queryRunner.run({
|
||||
timezone: 'browser',
|
||||
timeRange: { from: dateMath.parse(timeRange.from)!, to: dateMath.parse(timeRange.to)!, raw: timeRange },
|
||||
maxDataPoints: queryOptions.maxDataPoints ?? 100,
|
||||
minInterval: queryOptions.minInterval,
|
||||
queries: queryOptions.queries,
|
||||
datasource: queryOptions.dataSource.name!,
|
||||
});
|
||||
onIntervalChanged = (interval: string) => {
|
||||
this.props.queryOptionsChange({ ...this.props.queryOptions, minInterval: interval });
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -49,6 +43,13 @@ export class AlertingQueryEditor extends PureComponent<Props> {
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.container}>
|
||||
<h4>Queries</h4>
|
||||
<div className={styles.refreshWrapper}>
|
||||
<RefreshPicker
|
||||
onIntervalChanged={this.onIntervalChanged}
|
||||
onRefresh={this.onRunQueries}
|
||||
intervals={['15s', '30s']}
|
||||
/>
|
||||
</div>
|
||||
<QueryGroup
|
||||
queryRunner={queryRunner}
|
||||
options={queryOptions}
|
||||
@ -70,6 +71,7 @@ const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = s
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||
queryOptionsChange,
|
||||
onRunQueries,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AlertingQueryEditor);
|
||||
@ -85,6 +87,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
background-color: ${theme.colors.panelBg};
|
||||
height: 100%;
|
||||
`,
|
||||
refreshWrapper: css`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`,
|
||||
editorWrapper: css`
|
||||
border: 1px solid ${theme.colors.panelBorder};
|
||||
border-radius: ${theme.border.radius.md};
|
||||
|
@ -1,18 +1,21 @@
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
import { css } from 'emotion';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { TabsBar, TabContent, Tab, useStyles, Table } from '@grafana/ui';
|
||||
import { TabsBar, TabContent, Tab, useStyles, Icon } from '@grafana/ui';
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { PreviewQueryTab } from './PreviewQueryTab';
|
||||
import { PreviewInstancesTab } from './PreviewInstancesTab';
|
||||
|
||||
enum Tabs {
|
||||
Query = 'query',
|
||||
Instance = 'instance',
|
||||
Instances = 'instances',
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: Tabs.Query, text: 'Query', active: true },
|
||||
{ id: Tabs.Instance, text: 'Alerting instance', active: false },
|
||||
{ id: Tabs.Query, text: 'Query result' },
|
||||
{ id: Tabs.Instances, text: 'Alerting instances' },
|
||||
];
|
||||
|
||||
interface Props {
|
||||
@ -20,11 +23,12 @@ interface Props {
|
||||
}
|
||||
|
||||
export const AlertingQueryPreview: FC<Props> = ({ queryRunner }) => {
|
||||
const [activeTab, setActiveTab] = useState<string>('query');
|
||||
const [activeTab, setActiveTab] = useState<string>(Tabs.Query);
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), []);
|
||||
const data = useObservable(observable);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<TabsBar>
|
||||
@ -40,30 +44,65 @@ export const AlertingQueryPreview: FC<Props> = ({ queryRunner }) => {
|
||||
})}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
{activeTab === Tabs.Query && data && (
|
||||
<div>
|
||||
<Table data={data.series[0]} width={1200} height={300} />
|
||||
{data && data.state === 'Error' ? (
|
||||
<div className={styles.noQueries}>
|
||||
<h4 className={styles.noQueriesHeader}>There was an error :(</h4>
|
||||
<div>{data.error?.data?.error}</div>
|
||||
</div>
|
||||
) : data && data.series.length > 0 ? (
|
||||
<AutoSizer style={{ width: '100%', height: '100%' }}>
|
||||
{({ width, height }) => {
|
||||
switch (activeTab) {
|
||||
case Tabs.Instances:
|
||||
return <PreviewInstancesTab isTested={false} data={data} styles={styles} />;
|
||||
|
||||
case Tabs.Query:
|
||||
default:
|
||||
return <PreviewQueryTab data={data} width={width} height={height} />;
|
||||
}
|
||||
}}
|
||||
</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>
|
||||
)}
|
||||
{activeTab === Tabs.Instance && <div>Instance something something dark side</div>}
|
||||
</TabContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => {
|
||||
const tabBarHeight = 42;
|
||||
|
||||
return {
|
||||
wrapper: css`
|
||||
label: alertDefinitionPreviewTabs;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: ${theme.spacing.md} 0 0 ${theme.spacing.md};
|
||||
`,
|
||||
tabContent: css`
|
||||
background: ${theme.colors.panelBg};
|
||||
height: calc(100% - ${tabBarHeight}px);
|
||||
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>;
|
||||
|
@ -0,0 +1,23 @@
|
||||
import React, { FC } from 'react';
|
||||
import { PanelData } from '@grafana/data';
|
||||
import { Button } from '@grafana/ui';
|
||||
import { PreviewStyles } from './AlertingQueryPreview';
|
||||
|
||||
interface Props {
|
||||
data: PanelData;
|
||||
isTested: boolean;
|
||||
styles: PreviewStyles;
|
||||
}
|
||||
|
||||
export const PreviewInstancesTab: FC<Props> = ({ data, isTested, styles }) => {
|
||||
if (!isTested) {
|
||||
return (
|
||||
<div className={styles.noQueries}>
|
||||
<h4 className={styles.noQueriesHeader}>You haven’t tested your alert yet.</h4>
|
||||
<div>In order to see your instances, you need to test your alert first.</div>
|
||||
<Button>Test alert now</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div>Instances</div>;
|
||||
};
|
52
public/app/features/alerting/components/PreviewQueryTab.tsx
Normal file
52
public/app/features/alerting/components/PreviewQueryTab.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { getFrameDisplayName, GrafanaTheme, PanelData } from '@grafana/data';
|
||||
import { Select, stylesFactory, Table, useTheme } from '@grafana/ui';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface Props {
|
||||
data: PanelData;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const PreviewQueryTab: FC<Props> = ({ data, height, width }) => {
|
||||
const [currentSeries, setSeries] = useState<number>(0);
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme, height);
|
||||
const series = useMemo(
|
||||
() => data.series.map((frame, index) => ({ value: index, label: getFrameDisplayName(frame) })),
|
||||
[data.series]
|
||||
);
|
||||
|
||||
// Select padding
|
||||
const padding = 16;
|
||||
|
||||
if (data.series.length > 1) {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div style={{ height: height - theme.spacing.formInputHeight - 16 }}>
|
||||
<Table
|
||||
data={data.series[currentSeries]}
|
||||
height={height - theme.spacing.formInputHeight - padding}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.selectWrapper}>
|
||||
<Select onChange={selectedValue => setSeries(selectedValue.value!)} options={series} value={currentSeries} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <Table data={data.series[0]} height={height} width={width} />;
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, height: number) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
height: ${height}px;
|
||||
`,
|
||||
selectWrapper: css`
|
||||
padding: ${theme.spacing.md};
|
||||
`,
|
||||
};
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { AppEvents, dateMath } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
@ -14,7 +14,7 @@ import {
|
||||
setQueryOptions,
|
||||
} from './reducers';
|
||||
import { AlertDefinition, AlertDefinitionUiState, AlertRuleDTO, NotifierDTO, ThunkResult } from 'app/types';
|
||||
import { QueryGroupOptions } from '../../query/components/QueryGroupOptions';
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
|
||||
export function getAlertRulesAsync(options: { state: string }): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
@ -138,3 +138,19 @@ export function queryOptionsChange(queryOptions: QueryGroupOptions): ThunkResult
|
||||
dispatch(setQueryOptions(queryOptions));
|
||||
};
|
||||
}
|
||||
|
||||
export function onRunQueries(): ThunkResult<void> {
|
||||
return (dispatch, getStore) => {
|
||||
const { queryRunner, queryOptions } = getStore().alertDefinition;
|
||||
const timeRange = { from: 'now-1h', to: 'now' };
|
||||
|
||||
queryRunner.run({
|
||||
timezone: 'browser',
|
||||
timeRange: { from: dateMath.parse(timeRange.from)!, to: dateMath.parse(timeRange.to)!, raw: timeRange },
|
||||
maxDataPoints: queryOptions.maxDataPoints ?? 100,
|
||||
minInterval: queryOptions.minInterval,
|
||||
queries: queryOptions.queries,
|
||||
datasource: queryOptions.dataSource.name!,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -12,11 +12,11 @@ import {
|
||||
NotificationChannelOption,
|
||||
NotificationChannelState,
|
||||
NotifierDTO,
|
||||
QueryGroupOptions,
|
||||
} from 'app/types';
|
||||
import store from 'app/core/store';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { QueryGroupOptions } from '../../query/components/QueryGroupOptions';
|
||||
|
||||
export const ALERT_DEFINITION_UI_STATE_STORAGE_KEY = 'grafana.alerting.alertDefinition.ui';
|
||||
const DEFAULT_ALERT_DEFINITION_UI_STATE: AlertDefinitionUiState = { rightPaneSize: 400, topPaneSize: 0.45 };
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { QueryGroup } from 'app/features/query/components/QueryGroup';
|
||||
import { QueryGroupOptions } from 'app/features/query/components/QueryGroupOptions';
|
||||
import { PanelModel } from '../../state';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
|
@ -221,9 +221,9 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onDisableQuery = () => {
|
||||
this.props.query.hide = !this.props.query.hide;
|
||||
const { query } = this.props;
|
||||
this.props.onChange({ ...query, hide: !query.hide });
|
||||
this.props.onRunQuery();
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
renderCollapsedText(): string | null {
|
||||
|
@ -23,9 +23,10 @@ import { Unsubscribable } from 'rxjs';
|
||||
import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { PanelQueryRunner } from '../state/PanelQueryRunner';
|
||||
import { QueryGroupOptions, QueryGroupOptionsEditor } from './QueryGroupOptions';
|
||||
import { QueryGroupOptionsEditor } from './QueryGroupOptions';
|
||||
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
|
||||
import { css } from 'emotion';
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
|
||||
interface Props {
|
||||
queryRunner: PanelQueryRunner;
|
||||
|
@ -2,7 +2,7 @@
|
||||
import React, { PureComponent, ChangeEvent, FocusEvent } from 'react';
|
||||
|
||||
// Utils
|
||||
import { rangeUtil, PanelData, DataSourceApi, DataQuery } from '@grafana/data';
|
||||
import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data';
|
||||
|
||||
// Components
|
||||
import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui';
|
||||
@ -11,25 +11,7 @@ import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@gra
|
||||
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { config } from 'app/core/config';
|
||||
import { css } from 'emotion';
|
||||
|
||||
export interface QueryGroupOptions {
|
||||
queries: DataQuery[];
|
||||
dataSource: QueryGroupDataSource;
|
||||
maxDataPoints?: number | null;
|
||||
minInterval?: string | null;
|
||||
cacheTimeout?: string | null;
|
||||
timeRange?: {
|
||||
from?: string | null;
|
||||
shift?: string | null;
|
||||
hide?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface QueryGroupDataSource {
|
||||
name?: string | null;
|
||||
uid?: string;
|
||||
default?: boolean;
|
||||
}
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
|
||||
interface Props {
|
||||
options: QueryGroupOptions;
|
||||
|
@ -4,8 +4,8 @@ import { config } from 'app/core/config';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
import { QueryGroup } from '../query/components/QueryGroup';
|
||||
import { QueryGroupOptions } from '../query/components/QueryGroupOptions';
|
||||
import { PanelQueryRunner } from '../query/state/PanelQueryRunner';
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
|
||||
interface State {
|
||||
queryRunner: PanelQueryRunner;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PanelData, SelectableValue } from '@grafana/data';
|
||||
import { PanelQueryRunner } from '../features/query/state/PanelQueryRunner';
|
||||
import { QueryGroupOptions } from '../features/query/components/QueryGroupOptions';
|
||||
import { QueryGroupOptions } from './query';
|
||||
|
||||
export interface AlertRuleDTO {
|
||||
id: number;
|
||||
|
@ -15,6 +15,7 @@ export * from './store';
|
||||
export * from './ldap';
|
||||
export * from './appEvent';
|
||||
export * from './angular';
|
||||
export * from './query';
|
||||
|
||||
import * as CoreEvents from './events';
|
||||
export { CoreEvents };
|
||||
|
20
public/app/types/query.ts
Normal file
20
public/app/types/query.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { DataQuery } from '@grafana/data';
|
||||
|
||||
export interface QueryGroupOptions {
|
||||
queries: DataQuery[];
|
||||
dataSource: QueryGroupDataSource;
|
||||
maxDataPoints?: number | null;
|
||||
minInterval?: string | null;
|
||||
cacheTimeout?: string | null;
|
||||
timeRange?: {
|
||||
from?: string | null;
|
||||
shift?: string | null;
|
||||
hide?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface QueryGroupDataSource {
|
||||
name?: string | null;
|
||||
uid?: string;
|
||||
default?: boolean;
|
||||
}
|
Reference in New Issue
Block a user