mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Geomap: Add ability to select a data query filter for each layer (#49966)
* Fix random typo I found * add 'useDataFrame' boolean to each layer type to determine whether the layer queues off of query data * Add data frame picker to options layout depending on layer type * change layer update logic to render features from a specific data frame. Lift data frame selection up a level in order to add some more complex error handling. * add a todo to the MapLayerRegistryItem interface * update optional arg in function signature for consistency * move dataframe filtering to paneldata, revert layers to prior state * commit refactor, need to clean up still * pull copy-pasted code into its own function * clean up comments * Update layer.ts * remove unused types * fix spacing * improve dropdown * need to add dependency to this callback function, otherwise it will always use the context of the last layer * add data query recovery logic to handle query renames Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
9703c9211e
commit
1284c596fe
@ -3,7 +3,7 @@ import BaseLayer from 'ol/layer/Base';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '../themes';
|
||||
import { PanelData } from '../types';
|
||||
import { MatcherConfig, PanelData } from '../types';
|
||||
import { PanelOptionsEditorBuilder } from '../utils';
|
||||
import { RegistryItemWithOptions } from '../utils/Registry';
|
||||
|
||||
@ -58,6 +58,9 @@ export interface MapLayerOptions<TConfig = any> {
|
||||
// Common method to define geometry fields
|
||||
location?: FrameGeometrySource;
|
||||
|
||||
// Defines which data query refId is associated with the layer
|
||||
filterData?: MatcherConfig;
|
||||
|
||||
// Common properties:
|
||||
// https://openlayers.org/en/latest/apidoc/module-ol_layer_Base-BaseLayer.html
|
||||
// Layer opacity (0-1)
|
||||
@ -72,6 +75,9 @@ export interface MapLayerOptions<TConfig = any> {
|
||||
*/
|
||||
export interface MapLayerHandler<TConfig = any> {
|
||||
init: () => BaseLayer;
|
||||
/**
|
||||
* The update function should only be implemented if the layer type makes use of query data
|
||||
*/
|
||||
update?: (data: PanelData) => void;
|
||||
legend?: ReactNode;
|
||||
|
||||
|
@ -296,7 +296,7 @@ export const getStandardOptionEditors = () => {
|
||||
const fieldName: StandardEditorsRegistryItem<string, FieldNamePickerConfigSettings> = {
|
||||
id: 'field-name',
|
||||
name: 'Field name',
|
||||
description: 'Time zone selection',
|
||||
description: 'Allows selecting a field name from a data frame',
|
||||
editor: FieldNamePicker as any,
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,9 @@ import {
|
||||
DataHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
FrameGeometrySourceMode,
|
||||
getFrameMatchers,
|
||||
GrafanaTheme,
|
||||
MapLayerHandler,
|
||||
MapLayerOptions,
|
||||
PanelData,
|
||||
PanelProps,
|
||||
@ -251,9 +253,7 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
*/
|
||||
dataChanged(data: PanelData) {
|
||||
for (const state of this.layers) {
|
||||
if (state.handler.update) {
|
||||
state.handler.update(data);
|
||||
}
|
||||
this.applyLayerFilter(state.handler, state.options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,9 +468,7 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
group.setAt(layerIndex, info.layer);
|
||||
|
||||
// initialize with new data
|
||||
if (info.handler.update) {
|
||||
info.handler.update(this.props.data);
|
||||
}
|
||||
this.applyLayerFilter(info.handler, newOptions);
|
||||
} catch (err) {
|
||||
console.warn('ERROR', err);
|
||||
return false;
|
||||
@ -506,10 +504,6 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
const handler = await item.create(map, options, config.theme2);
|
||||
const layer = handler.init();
|
||||
|
||||
if (handler.update) {
|
||||
handler.update(this.props.data);
|
||||
}
|
||||
|
||||
if (!options.name) {
|
||||
options.name = this.getNextLayerName();
|
||||
}
|
||||
@ -533,9 +527,26 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
|
||||
this.byName.set(UID, state);
|
||||
(state.layer as any).__state = state;
|
||||
|
||||
this.applyLayerFilter(handler, options);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
applyLayerFilter(handler: MapLayerHandler<any>, options: MapLayerOptions<any>): void {
|
||||
if (handler.update) {
|
||||
let panelData = this.props.data;
|
||||
if (options.filterData) {
|
||||
const matcherFunc = getFrameMatchers(options.filterData);
|
||||
panelData = {
|
||||
...panelData,
|
||||
series: panelData.series.filter(matcherFunc),
|
||||
};
|
||||
}
|
||||
handler.update(panelData);
|
||||
}
|
||||
}
|
||||
|
||||
initMapView(config: MapViewConfig, layers?: Collection<BaseLayer>): View {
|
||||
let view = new View({
|
||||
center: [0, 0],
|
||||
|
@ -6,6 +6,7 @@ import { hasAlphaPanels } from 'app/core/config';
|
||||
import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils';
|
||||
import { addLocationFields } from 'app/features/geo/editor/locationEditor';
|
||||
|
||||
import { FrameSelectionEditor } from '../layers/data/FrameSelectionEditor';
|
||||
import { defaultMarkersConfig } from '../layers/data/markersLayer';
|
||||
import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from '../layers/registry';
|
||||
import { MapLayerState } from '../types';
|
||||
@ -76,6 +77,17 @@ export function getLayerEditor(opts: LayerEditorOptions): NestedPanelOptions<Map
|
||||
},
|
||||
});
|
||||
|
||||
// Show data filter if the layer type can do something with the data query results
|
||||
if (handler.update) {
|
||||
builder.addCustomEditor({
|
||||
id: 'filterData',
|
||||
path: 'filterData',
|
||||
name: 'Data',
|
||||
editor: FrameSelectionEditor,
|
||||
defaultValue: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (!layer) {
|
||||
return; // unknown layer type
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
||||
import { FrameMatcherID, getFieldDisplayName, MatcherConfig, SelectableValue, StandardEditorProps } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
const recoverRefIdMissing = (newRefIds: SelectableValue[], oldRefIds: SelectableValue[], previousValue: string | undefined): SelectableValue | undefined => {
|
||||
if (!previousValue) {
|
||||
return;
|
||||
}
|
||||
// Previously selected value is missing from the new list.
|
||||
// Find the value that is in the new list but isn't in the old list
|
||||
let changedTo = newRefIds.find((refId) => {
|
||||
return !oldRefIds.some((refId2) => {
|
||||
return refId === refId2;
|
||||
});
|
||||
});
|
||||
if (changedTo) {
|
||||
// Found the new value, we assume the old value changed to this one, so we'll use it
|
||||
return changedTo;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
export const FrameSelectionEditor: FC<StandardEditorProps<MatcherConfig>> = ({
|
||||
value,
|
||||
context,
|
||||
onChange,
|
||||
item,
|
||||
}) => {
|
||||
const listOfRefId = useMemo(() => {
|
||||
return context.data.map(f => ({
|
||||
value: f.refId,
|
||||
label: `Query: ${f.refId} (size: ${f.length})`,
|
||||
description: f.fields.map(f => getFieldDisplayName(f)).join(', '),
|
||||
}));
|
||||
}, [context.data]);
|
||||
|
||||
const [priorSelectionState, updatePriorSelectionState] = useState({
|
||||
refIds: [] as SelectableValue[],
|
||||
value: undefined as string | undefined,
|
||||
});
|
||||
|
||||
const currentValue = useMemo(() => {
|
||||
return listOfRefId.find((refId) => refId.value === value?.options) ?? recoverRefIdMissing(listOfRefId, priorSelectionState.refIds, priorSelectionState.value);
|
||||
}, [value, listOfRefId])
|
||||
|
||||
const onFilterChange = useCallback((v: SelectableValue<string>) => {
|
||||
onChange(v?.value ? {
|
||||
"id": FrameMatcherID.byRefId,
|
||||
"options": v.value
|
||||
} : undefined);
|
||||
}, [context.options.name]);
|
||||
|
||||
if (listOfRefId !== priorSelectionState.refIds || currentValue?.value !== priorSelectionState.value) {
|
||||
updatePriorSelectionState({
|
||||
refIds: listOfRefId,
|
||||
value: currentValue?.value
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Select options={listOfRefId} onChange={onFilterChange} isClearable={true} placeholder="Change filter" value={currentValue}/>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user