Skip to content
Closed
103 changes: 97 additions & 6 deletions src/datasources/data-frame/DataFrameDataSource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import TTLCache from '@isaacs/ttlcache';
import deepEqual from 'fast-deep-equal';
import { DataQueryRequest, DataSourceInstanceSettings, FieldType, TimeRange, FieldDTO, dateTime, DataFrameDTO, MetricFindValue, TestDataSourceResponse, DataSourceJsonData } from '@grafana/data';
import { BackendSrv, TemplateSrv, getBackendSrv, getTemplateSrv } from '@grafana/runtime';
import { BackendSrv, TemplateSrv, getBackendSrv, getTemplateSrv, locationService } from '@grafana/runtime';
import {
ColumnDataType,
DataFrameQuery,
Expand Down Expand Up @@ -35,12 +35,21 @@ export class DataFrameDataSource extends DataSourceBase<DataFrameQuery, DataSour

defaultQuery = defaultQuery;

async runQuery(query: DataFrameQuery, { range, scopedVars, maxDataPoints }: DataQueryRequest): Promise<DataFrameDTO> {
async runQuery(
query: DataFrameQuery,
request: DataQueryRequest
): Promise<DataFrameDTO> {
const { range, scopedVars, maxDataPoints } = request;
const processedQuery = this.processQuery(query);
processedQuery.tableId = this.templateSrv.replace(processedQuery.tableId, scopedVars);
processedQuery.columns = replaceVariables(processedQuery.columns, this.templateSrv);
const properties = await this.getTableProperties(processedQuery.tableId);

// this.initializeFetchHighResolutionData(
// processedQuery.fetchHighResolutionData,
// request.panelId?.toString()
// );

if (processedQuery.type === DataFrameQueryType.Properties) {
return {
refId: processedQuery.refId,
Expand All @@ -49,7 +58,13 @@ export class DataFrameDataSource extends DataSourceBase<DataFrameQuery, DataSour
};
} else {
const columns = this.getColumnTypes(processedQuery.columns, properties?.columns ?? []);
const tableData = await this.getDecimatedTableData(processedQuery, columns, range, maxDataPoints);
const tableData = await this.getDecimatedTableData(
processedQuery,
columns,
range,
maxDataPoints,
// request.panelId?.toString()
);
return {
refId: processedQuery.refId,
name: properties.name,
Expand All @@ -74,11 +89,41 @@ export class DataFrameDataSource extends DataSourceBase<DataFrameQuery, DataSour
return properties;
}

async getDecimatedTableData(query: DataFrameQuery, columns: Column[], timeRange: TimeRange, intervals = 1000): Promise<TableDataRows> {
async getDecimatedTableData(
query: DataFrameQuery,
columns: Column[],
timeRange: TimeRange,
intervals = 1000,
// panelId = ''
): Promise<TableDataRows> {
const filters: ColumnFilter[] = [];

if (query.applyTimeFilters) {
filters.push(...this.constructTimeFilters(columns, timeRange));
// let enabledPanelIds: string[] = this.getEnabledPanelIds();
// const isHighResolutionEnabled = enabledPanelIds.includes(panelId) && columns.length > 0;
const isHighResolutionEnabled = query.fetchHighResolutionData;
if (isHighResolutionEnabled) {
/**
* If `x-axis` selection is of type "TIMESTAMP", the below filters should be applied.
* Only after data source migration, we'll get those details here as a part of `columns`. Hence just handled numerical values for now.
* filters.push(...this.constructTimeFilters(columns, timeRange));
*/

const queryParams = locationService.getSearchObject();
// Once we have a seprate control to select x-axis, we can use that value directly instead of assuming the first column as the x-axis.
const xField = columns[0].name;
const xMin = queryParams[`${xField}-min`];
const xMax = queryParams[`${xField}-max`];
if (
xMin !== ''
&& xMax !== ''
&& xMin !== undefined
&& xMax !== undefined
// For session storage
// && xMin !== null
// && xMax !== null
) {
filters.push(...this.constructXAxisNumberFilters(xField, Math.floor(Number(xMin)), Math.floor(Number(xMax))));
}
}

if (query.filterNulls) {
Expand Down Expand Up @@ -124,6 +169,45 @@ export class DataFrameDataSource extends DataSourceBase<DataFrameQuery, DataSour
return deepEqual(migratedQuery, query) ? (query as ValidDataFrameQuery) : migratedQuery;
}

// initializeFetchHighResolutionData(
// enableHighResolutionZoom: boolean,
// panelId = ''
// ): void {
// if (panelId === '' || this.isInEditPanelMode()) {
// return;
// }

// let enabledPanelIds: string[] = this.getEnabledPanelIds();
// if (enableHighResolutionZoom && !enabledPanelIds.includes(panelId)) {
// enabledPanelIds.push(panelId);
// }

// locationService.partial({
// [`fetchHighResolutionData`]: enabledPanelIds.join(','),
// }, true);
// }

// getEnabledPanelIds(): string[] {
// const queryParams = locationService.getSearchObject();
// const fetchHighResolutionDataOnZoom = queryParams['fetchHighResolutionData'];
// let enabledPanelIds: string[] = [];

// if (
// fetchHighResolutionDataOnZoom !== undefined
// && typeof fetchHighResolutionDataOnZoom === 'string'
// && fetchHighResolutionDataOnZoom !== ''
// ) {
// enabledPanelIds = fetchHighResolutionDataOnZoom.split(',');
// }

// return enabledPanelIds;
// }

isInEditPanelMode(): boolean {
const queryParams = locationService.getSearchObject();
return queryParams['editPanel'] !== undefined;
}

async metricFindQuery(tableQuery: DataFrameQuery): Promise<MetricFindValue[]> {
const tableProperties = await this.getTableProperties(tableQuery.tableId);
return tableProperties.columns.map(col => ({ text: col.name, value: col.name }));
Expand Down Expand Up @@ -179,6 +263,13 @@ export class DataFrameDataSource extends DataSourceBase<DataFrameQuery, DataSour
];
}

private constructXAxisNumberFilters(xField: string, xMin: number, xMax: number): ColumnFilter[] {
return [
{ column: xField, operation: 'GREATER_THAN_EQUALS', value: xMin.toString() },
{ column: xField, operation: 'LESS_THAN_EQUALS', value: xMax.toString() },
];
}

private constructNullFilters(columns: Column[]): ColumnFilter[] {
return columns.flatMap(({ name, columnType, dataType }) => {
const filters: ColumnFilter[] = [];
Expand Down
80 changes: 73 additions & 7 deletions src/datasources/data-frame/components/DataFrameQueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { useAsync } from 'react-use';
import React, { useEffect, useMemo, useState } from 'react';
import { useAsync, useLocation } from 'react-use';
import { SelectableValue, toOption } from '@grafana/data';
import { InlineField, InlineSwitch, MultiSelect, Select, AsyncSelect, RadioButtonGroup } from '@grafana/ui';
import { decimationMethods } from '../constants';
import _ from 'lodash';
import { getTemplateSrv } from '@grafana/runtime';
import { getTemplateSrv, locationService } from '@grafana/runtime';
import { isValidId } from '../utils';
import { FloatingError, parseErrorMessage } from '../../../core/errors';
import { DataFrameQueryEditorCommon, Props } from './DataFrameQueryEditorCommon';
Expand All @@ -14,7 +14,10 @@ import { DataFrameQueryType } from '../types';
export const DataFrameQueryEditor = (props: Props) => {
const [errorMsg, setErrorMsg] = useState<string | undefined>('');
const handleError = (error: Error) => setErrorMsg(parseErrorMessage(error));
const common = new DataFrameQueryEditorCommon(props, handleError);
const common = useMemo(
() => new DataFrameQueryEditorCommon(props, handleError)
, [props]
);
const tableProperties = useAsync(() => common.datasource.getTableProperties(common.query.tableId).catch(handleError), [common.query.tableId]);

const handleColumnChange = (items: Array<SelectableValue<string>>) => {
Expand All @@ -27,6 +30,68 @@ export const DataFrameQueryEditor = (props: Props) => {
return columnOptions;
}

// When the user toggles the "Fetch high resolution data on zoom" switch, we update the URL query parameters
// const updateFetchHighResolutionDataStateInQueryParams = (fetchHighResolutionData: boolean) => {
// const editPanelId = getPanelId();
// let fetchHighResolutionDataEnabledPanelIds: string[] = getEnabledPanelIds();

// if (fetchHighResolutionData && !fetchHighResolutionDataEnabledPanelIds.includes(editPanelId)) {
// fetchHighResolutionDataEnabledPanelIds.push(editPanelId);
// }

// if (!fetchHighResolutionData && fetchHighResolutionDataEnabledPanelIds.includes(editPanelId)) {
// fetchHighResolutionDataEnabledPanelIds = fetchHighResolutionDataEnabledPanelIds.filter((panelId) => panelId !== editPanelId);
// }

// locationService.partial({
// [`fetchHighResolutionData`]: fetchHighResolutionDataEnabledPanelIds.join(','),
// }, true);
// }

// When URL query parameters change, we check if the current panel is enabled for fetching high resolution data and update the state in all the queries available in that panel - To sync the state across queries.
// const location = useLocation();
// useEffect(() => {
// const editPanelId = getPanelId();
// const fetchHighResolutionDataEnabledPanelIds: string[] = getEnabledPanelIds();

// const updatedFetchHighResolutionDataState = fetchHighResolutionDataEnabledPanelIds.includes(editPanelId ?? '');
// if (updatedFetchHighResolutionDataState !== common.query.fetchHighResolutionData) {
// common.handleQueryChange({ ...common.query, fetchHighResolutionData: updatedFetchHighResolutionDataState }, true);
// }
// }, [location.search, common]);

// Utility functions
// const getEnabledPanelIds = (): string[] => {
// const queryParams = locationService.getSearchObject();
// const fetchHighResolutionDataOnZoom = queryParams['fetchHighResolutionData'];
// let enabledPanelIds: string[] = [];

// if (
// fetchHighResolutionDataOnZoom !== undefined
// && typeof fetchHighResolutionDataOnZoom === 'string'
// && fetchHighResolutionDataOnZoom !== ''
// ) {
// enabledPanelIds = fetchHighResolutionDataOnZoom.split(',');
// }

// return enabledPanelIds;
// }

// const getPanelId = (): string => {
// const queryParams = locationService.getSearchObject();
// const editPanelId = queryParams['editPanel'];

// if (
// editPanelId !== undefined
// && (typeof editPanelId === 'string' || typeof editPanelId === 'number')
// && editPanelId !== ''
// ) {
// return editPanelId.toString();
// }
// return '';
// }


return (
<div style={{ position: 'relative' }}>
<InlineField label="Query type" tooltip={tooltips.queryType}>
Expand Down Expand Up @@ -74,10 +139,11 @@ export const DataFrameQueryEditor = (props: Props) => {
onChange={event => common.handleQueryChange({ ...common.query, filterNulls: event.currentTarget.checked }, true)}
></InlineSwitch>
</InlineField>
<InlineField label="Use time range" tooltip={tooltips.useTimeRange}>
<InlineField label="Fetch high resolution data on zoom" tooltip={tooltips.useTimeRange}>
<InlineSwitch
value={common.query.applyTimeFilters}
onChange={event => common.handleQueryChange({ ...common.query, applyTimeFilters: event.currentTarget.checked }, true)}
value={common.query.fetchHighResolutionData}
// onChange={event => updateFetchHighResolutionDataStateInQueryParams(event.currentTarget.checked)}
onChange={event => common.handleQueryChange({ ...common.query, fetchHighResolutionData: event.currentTarget.checked }, true)}
></InlineSwitch>
</InlineField>
</>
Expand Down
4 changes: 2 additions & 2 deletions src/datasources/data-frame/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface DataFrameQuery extends DataQuery {
columns?: string[];
decimationMethod?: string;
filterNulls?: boolean;
applyTimeFilters?: boolean;
fetchHighResolutionData?: boolean;
}

export const defaultQuery: Omit<ValidDataFrameQuery, 'refId'> = {
Expand All @@ -21,7 +21,7 @@ export const defaultQuery: Omit<ValidDataFrameQuery, 'refId'> = {
columns: [],
decimationMethod: 'LOSSY',
filterNulls: false,
applyTimeFilters: false
fetchHighResolutionData: false
};

export type ValidDataFrameQuery = DataFrameQuery & Required<Omit<DataFrameQuery, keyof DataQuery>>;
Expand Down
22 changes: 19 additions & 3 deletions src/panels/plotly/PlotlyPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@grafana/data';
import { AxisLabels, PanelOptions } from './types';
import { useTheme2, ContextMenu, MenuItemsGroup, linkModelToContextMenuItems } from '@grafana/ui';
import { getTemplateSrv, PanelDataErrorView } from '@grafana/runtime';
import { getTemplateSrv, PanelDataErrorView, locationService } from '@grafana/runtime';
import { getFieldsByName, notEmpty, Plot, renderMenuItems, useTraceColors } from './utils';
import { AxisType, Legend, PlotData, PlotType, toImage, Icons, PlotlyHTMLElement } from 'plotly.js-basic-dist-min';
import { saveAs } from 'file-saver';
Expand All @@ -28,7 +28,7 @@ interface MenuState {
interface Props extends PanelProps<PanelOptions> {}

export const PlotlyPanel: React.FC<Props> = (props) => {
const { data, width, height, options } = props;
const { data, width, height, options, id } = props;
const [menu, setMenu] = useState<MenuState>({ x: 0, y: 0, show: false, items: [] });
const theme = useTheme2();

Expand Down Expand Up @@ -153,7 +153,23 @@ export const PlotlyPanel: React.FC<Props> = (props) => {
props.onOptionsChange({...options, xAxis: { ...options.xAxis, min: from.valueOf(), max: to.valueOf() } });
}
} else {
props.onOptionsChange({...options, xAxis: { ...options.xAxis, min: xAxisMin, max: xAxisMax } });
// const queryParams = locationService.getSearchObject();
// const fetchHighResolutionDataOnZoom = queryParams['fetchHighResolutionData'];

// if (
// fetchHighResolutionDataOnZoom !== undefined
// && typeof fetchHighResolutionDataOnZoom === 'string'
// && fetchHighResolutionDataOnZoom !== ''
// && fetchHighResolutionDataOnZoom.split(',').includes(id.toString())
// ) {
locationService.partial({
[`${options.xAxis.field}-min`]: Math.floor(xAxisMin),
[`${options.xAxis.field}-max`]: Math.ceil(xAxisMax)
}, true);
(document.querySelector('[aria-label="Refresh dashboard"]') as HTMLButtonElement).click()
// } else {
// props.onOptionsChange({...options, xAxis: { ...options.xAxis, min: xAxisMin, max: xAxisMax } });
// }
}
};

Expand Down
Loading