import { omit } from 'lodash'; import React, { PureComponent, useState } from 'react'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; import { DataQuery, DataSourceInstanceSettings, LoadingState, PanelData, RelativeTimeRange } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; import { Button, Card, Icon } from '@grafana/ui'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertQueryOptions, EmptyQueryWrapper, QueryWrapper } from './QueryWrapper'; import { errorFromSeries, getThresholdsForQueries } from './util'; interface Props { // The query configuration queries: AlertQuery[]; expressions: AlertQuery[]; data: Record; onRunQueries: () => void; // Query editing onQueriesChange: (queries: AlertQuery[]) => void; onDuplicateQuery: (query: AlertQuery) => void; condition: string | null; onSetCondition: (refId: string) => void; } export class QueryRows extends PureComponent { constructor(props: Props) { super(props); } onRemoveQuery = (query: DataQuery) => { const { queries, onQueriesChange } = this.props; onQueriesChange(queries.filter((q) => q.refId !== query.refId)); }; onChangeTimeRange = (timeRange: RelativeTimeRange, index: number) => { const { queries, onQueriesChange } = this.props; onQueriesChange( queries.map((item, itemIndex) => { if (itemIndex !== index) { return item; } return { ...item, relativeTimeRange: timeRange, }; }) ); }; onChangeQueryOptions = (options: AlertQueryOptions, index: number) => { const { queries, onQueriesChange } = this.props; onQueriesChange( queries.map((item, itemIndex) => { if (itemIndex !== index) { return item; } return { ...item, model: { ...item.model, maxDataPoints: options.maxDataPoints }, }; }) ); }; onChangeDataSource = (settings: DataSourceInstanceSettings, index: number) => { const { queries, onQueriesChange } = this.props; const updatedQueries = queries.map((item, itemIndex) => { if (itemIndex !== index) { return item; } const previousSettings = this.getDataSourceSettings(item); // Copy model if changing to a datasource of same type. if (settings.type === previousSettings?.type) { return copyModel(item, settings); } return newModel(item, settings); }); onQueriesChange(updatedQueries); }; onChangeQuery = (query: DataQuery, index: number) => { const { queries, onQueriesChange } = this.props; onQueriesChange( queries.map((item, itemIndex) => { if (itemIndex !== index) { return item; } return { ...item, refId: query.refId, queryType: item.model.queryType ?? '', model: { ...item.model, ...query, datasource: query.datasource!, }, }; }) ); }; onDragEnd = (result: DropResult) => { const { queries, onQueriesChange } = this.props; if (!result || !result.destination) { return; } const startIndex = result.source.index; const endIndex = result.destination.index; if (startIndex === endIndex) { return; } const update = Array.from(queries); const [removed] = update.splice(startIndex, 1); update.splice(endIndex, 0, removed); onQueriesChange(update); }; getDataSourceSettings = (query: AlertQuery): DataSourceInstanceSettings | undefined => { return getDataSourceSrv().getInstanceSettings(query.datasourceUid); }; render() { const { queries, expressions } = this.props; const thresholdByRefId = getThresholdsForQueries([...queries, ...expressions]); return ( {(provided) => { return (
{queries.map((query, index) => { const data: PanelData = this.props.data?.[query.refId] ?? { series: [], state: LoadingState.NotStarted, }; const dsSettings = this.getDataSourceSettings(query); const isAlertCondition = this.props.condition === query.refId; const error = isAlertCondition ? errorFromSeries(data.series) : undefined; if (!dsSettings) { return ( { const defaultDataSource = getDatasourceSrv().getInstanceSettings(null); if (defaultDataSource) { this.onChangeDataSource(defaultDataSource, index); } }} onRemoveQuery={() => { this.onRemoveQuery(query); }} /> ); } return ( ); })} {provided.placeholder}
); }}
); } } function copyModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit { return { ...item, model: { ...omit(item.model, 'datasource'), datasource: { type: settings.type, uid: settings.uid, }, }, datasourceUid: settings.uid, }; } function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit { return { refId: item.refId, relativeTimeRange: item.relativeTimeRange, queryType: '', datasourceUid: settings.uid, model: { refId: item.refId, hide: false, datasource: { type: settings.type, uid: settings.uid, }, }, }; } interface DatasourceNotFoundProps { index: number; model: AlertDataQuery; onUpdateDatasource: () => void; onRemoveQuery: () => void; } const DatasourceNotFound = ({ index, onUpdateDatasource, onRemoveQuery, model }: DatasourceNotFoundProps) => { const refId = model.refId; const [showDetails, setShowDetails] = useState(false); const toggleDetails = () => { setShowDetails((show) => !show); }; const handleUpdateDatasource = () => { onUpdateDatasource(); }; return ( This datasource has been removed The datasource for this query was not found, it was either removed or is not installed correctly. {showDetails && (
              {JSON.stringify(model, null, 2)}
            
)}
); };