diff --git a/.betterer.results b/.betterer.results index be752e02af9..8a2aea3ca32 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5656,18 +5656,16 @@ exports[`better eslint`] = { "public/app/features/search/service/bluge.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Do not use any type assertions.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Do not use any type assertions.", "6"], [0, 0, 0, "Do not use any type assertions.", "7"], [0, 0, 0, "Do not use any type assertions.", "8"], - [0, 0, 0, "Do not use any type assertions.", "9"], + [0, 0, 0, "Unexpected any. Specify a different type.", "9"], [0, 0, 0, "Do not use any type assertions.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Do not use any type assertions.", "12"], - [0, 0, 0, "Do not use any type assertions.", "13"] + [0, 0, 0, "Do not use any type assertions.", "11"] ], "public/app/features/search/service/sql.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -7042,7 +7040,8 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "7"], [0, 0, 0, "Do not use any type assertions.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"] + [0, 0, 0, "Unexpected any. Specify a different type.", "10"], + [0, 0, 0, "Do not use any type assertions.", "11"] ], "public/app/plugins/datasource/graphite/components/FunctionEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -8698,6 +8697,19 @@ exports[`better eslint`] = { "public/app/plugins/panel/geomap/components/DataHoverView.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/geomap/components/MarkersLegend.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"], + [0, 0, 0, "Do not use any type assertions.", "6"] + ], + "public/app/plugins/panel/geomap/editor/FrameSelectionEditor.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], "public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -8716,6 +8728,35 @@ exports[`better eslint`] = { "public/app/plugins/panel/geomap/editor/MapViewEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/plugins/panel/geomap/editor/StyleEditor.tsx:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Do not use any type assertions.", "7"], + [0, 0, 0, "Unexpected any. Specify a different type.", "8"], + [0, 0, 0, "Do not use any type assertions.", "9"], + [0, 0, 0, "Unexpected any. Specify a different type.", "10"], + [0, 0, 0, "Do not use any type assertions.", "11"], + [0, 0, 0, "Unexpected any. Specify a different type.", "12"], + [0, 0, 0, "Do not use any type assertions.", "13"], + [0, 0, 0, "Unexpected any. Specify a different type.", "14"], + [0, 0, 0, "Do not use any type assertions.", "15"], + [0, 0, 0, "Unexpected any. Specify a different type.", "16"], + [0, 0, 0, "Do not use any type assertions.", "17"], + [0, 0, 0, "Unexpected any. Specify a different type.", "18"], + [0, 0, 0, "Do not use any type assertions.", "19"], + [0, 0, 0, "Unexpected any. Specify a different type.", "20"], + [0, 0, 0, "Do not use any type assertions.", "21"], + [0, 0, 0, "Unexpected any. Specify a different type.", "22"], + [0, 0, 0, "Do not use any type assertions.", "23"], + [0, 0, 0, "Unexpected any. Specify a different type.", "24"], + [0, 0, 0, "Do not use any type assertions.", "25"], + [0, 0, 0, "Unexpected any. Specify a different type.", "26"] + ], "public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/public/app/features/dimensions/types.ts b/public/app/features/dimensions/types.ts index d245399d26a..8e33109b9d5 100644 --- a/public/app/features/dimensions/types.ts +++ b/public/app/features/dimensions/types.ts @@ -12,7 +12,7 @@ export interface DimensionSupplier { isAssumed?: boolean; /** - * The fied used for + * The field used for */ field?: Field; diff --git a/public/app/features/search/service/bluge.ts b/public/app/features/search/service/bluge.ts index e66f73d45a7..1539619ba5d 100644 --- a/public/app/features/search/service/bluge.ts +++ b/public/app/features/search/service/bluge.ts @@ -1,9 +1,9 @@ import { lastValueFrom } from 'rxjs'; import { ArrayVector, DataFrame, DataFrameView, getDisplayProcessor, SelectableValue } from '@grafana/data'; -import { config, getDataSourceSrv } from '@grafana/runtime'; +import { config } from '@grafana/runtime'; import { TermCount } from 'app/core/components/TagFilter/TagFilter'; -import { GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; +import { getGrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { replaceCurrentFolderQuery } from './utils'; @@ -19,7 +19,7 @@ export class BlugeSearcher implements GrafanaSearcher { } async tags(query: SearchQuery): Promise { - const ds = (await getDataSourceSrv().get('-- Grafana --')) as GrafanaDatasource; + const ds = await getGrafanaDatasource(); const target = { refId: 'TagsQuery', queryType: GrafanaQueryType.Search, @@ -74,7 +74,7 @@ const nextPageSizes = 100; async function doSearchQuery(query: SearchQuery): Promise { query = await replaceCurrentFolderQuery(query); - const ds = (await getDataSourceSrv().get('-- Grafana --')) as GrafanaDatasource; + const ds = await getGrafanaDatasource(); const target = { refId: 'Search', queryType: GrafanaQueryType.Search, diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 390715d59e2..4407b6e5d9a 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -18,6 +18,7 @@ import { import { DataSourceWithBackend, getBackendSrv, + getDataSourceSrv, getGrafanaLiveSrv, getTemplateSrv, StreamingFrameOptions, @@ -211,6 +212,11 @@ export class GrafanaDatasource extends DataSourceWithBackend { } } +/** Get the GrafanaDatasource instance */ +export async function getGrafanaDatasource() { + return (await getDataSourceSrv().get('-- Grafana --')) as GrafanaDatasource; +} + export interface FileElement { name: string; ['media-type']: string; diff --git a/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx b/public/app/plugins/panel/geomap/components/MarkersLegend.tsx similarity index 89% rename from public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx rename to public/app/plugins/panel/geomap/components/MarkersLegend.tsx index 83a6aa680f5..444e6232ff4 100644 --- a/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx +++ b/public/app/plugins/panel/geomap/components/MarkersLegend.tsx @@ -1,18 +1,20 @@ -import React, { useMemo } from 'react'; -import { useStyles2, VizLegendItem } from '@grafana/ui'; -import { DataFrame, formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import BaseLayer from 'ol/layer/Base'; +import React, { useMemo } from 'react'; +import SVG from 'react-inlinesvg'; +import { useObservable } from 'react-use'; +import { of } from 'rxjs'; + +import { DataFrame, formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; +import { getMinMaxAndDelta } from '@grafana/data/src/field/scale'; +import { useStyles2, VizLegendItem } from '@grafana/ui'; +import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { config } from 'app/core/config'; import { DimensionSupplier } from 'app/features/dimensions'; import { getThresholdItems } from 'app/plugins/panel/state-timeline/utils'; -import { getMinMaxAndDelta } from '@grafana/data/src/field/scale'; -import SVG from 'react-inlinesvg'; -import { StyleConfigState } from '../../style/types'; -import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; -import { useObservable } from 'react-use'; -import { of } from 'rxjs'; -import BaseLayer from 'ol/layer/Base'; -import { MapLayerState } from '../../types'; + +import { StyleConfigState } from '../style/types'; +import { MapLayerState } from '../types'; export interface MarkersLegendProps { size?: DimensionSupplier; @@ -48,7 +50,7 @@ export function MarkersLegend(props: MarkersLegendProps) { return <>; } - const { color, opacity} = styleConfig?.base ?? {}; + const { color, opacity } = styleConfig?.base ?? {}; const symbol = styleConfig?.config.symbol?.fixed; if (color && symbol && !colorField) { @@ -64,7 +66,7 @@ export function MarkersLegend(props: MarkersLegendProps) { /> - ) + ); } if (!colorField) { @@ -87,12 +89,21 @@ export function MarkersLegend(props: MarkersLegendProps) { // ] // }) - const display = colorField.display ? (v: number) => formattedValueToString(colorField.display!(v)) : (v: number) => `${v}`; + const display = colorField.display + ? (v: number) => formattedValueToString(colorField.display!(v)) + : (v: number) => `${v}`; return (
{layerName}
- +
); diff --git a/public/app/plugins/panel/geomap/editor/FrameSelectionEditor.tsx b/public/app/plugins/panel/geomap/editor/FrameSelectionEditor.tsx new file mode 100644 index 00000000000..1126a05860a --- /dev/null +++ b/public/app/plugins/panel/geomap/editor/FrameSelectionEditor.tsx @@ -0,0 +1,84 @@ +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> = ({ 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, priorSelectionState]); + + const onFilterChange = useCallback( + (v: SelectableValue) => { + onChange( + v?.value + ? { + id: FrameMatcherID.byRefId, + options: v.value, + } + : undefined + ); + }, + [onChange] + ); + + if (listOfRefId !== priorSelectionState.refIds || currentValue?.value !== priorSelectionState.value) { + updatePriorSelectionState({ + refIds: listOfRefId, + value: currentValue?.value, + }); + } + return ( + - ); -}; diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts b/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts index 5dcc7b741fd..075122ad81d 100644 --- a/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts +++ b/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts @@ -1,10 +1,8 @@ import { MapLayerRegistryItem, MapLayerOptions, - PanelData, GrafanaTheme2, PluginState, - SelectableValue, EventBus, } from '@grafana/data'; import Map from 'ol/Map'; @@ -20,12 +18,11 @@ import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor'; import { defaultStyleConfig, StyleConfig, StyleConfigState } from '../../style/types'; import { getStyleConfigState } from '../../style/utils'; import { polyStyle } from '../../style/markers'; -import { StyleEditor } from './StyleEditor'; +import { StyleEditor } from '../../editor/StyleEditor'; import { ReplaySubject } from 'rxjs'; import { map as rxjsmap, first } from 'rxjs/operators'; import { getLayerPropertyInfo } from '../../utils/getFeatures'; -import { GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; -import { getDataSourceSrv } from '@grafana/runtime'; +import { getPublicGeoJSONFiles } from '../../utils/utils'; export interface GeoJSONMapperConfig { // URL for a geojson file @@ -60,8 +57,6 @@ export const DEFAULT_STYLE_RULE: FeatureStyleConfig = { }, }; -let publicGeoJSONFiles: Array> | undefined = undefined; - export const geojsonLayer: MapLayerRegistryItem = { id: 'geojson', name: 'GeoJSON', @@ -153,9 +148,6 @@ export const geojsonLayer: MapLayerRegistryItem = { return { init: () => vectorLayer, - update: (data: PanelData) => { - console.log('todo... find values matching the ID and update'); - }, registerOptionsUI: (builder) => { // get properties for first feature to use as ui options const layerInfo = features.pipe( @@ -163,16 +155,12 @@ export const geojsonLayer: MapLayerRegistryItem = { rxjsmap((v) => getLayerPropertyInfo(v)) ); - if (!publicGeoJSONFiles) { - initGeojsonFiles(); - } - builder .addSelect({ path: 'config.src', name: 'GeoJSON URL', settings: { - options: publicGeoJSONFiles ?? [], + options: getPublicGeoJSONFiles() ?? [], allowCustomValue: true, }, defaultValue: defaultOptions.src, @@ -207,27 +195,3 @@ export const geojsonLayer: MapLayerRegistryItem = { defaultOptions, }; -// This will find all geojson files in the maps and gazetteer folders -async function initGeojsonFiles() { - if (publicGeoJSONFiles) { - return; - } - publicGeoJSONFiles = []; - - const ds = (await getDataSourceSrv().get('-- Grafana --')) as GrafanaDatasource; - for (let folder of ['maps', 'gazetteer']) { - ds.listFiles(folder).subscribe({ - next: (frame) => { - frame.forEach((item) => { - if (item.name.endsWith('.geojson')) { - const value = `public/${folder}/${item.name}`; - publicGeoJSONFiles!.push({ - value, - label: value, - }); - } - }); - }, - }); - } -} diff --git a/public/app/plugins/panel/geomap/layers/data/index.ts b/public/app/plugins/panel/geomap/layers/data/index.ts index 8f325b0a456..871352c3a36 100644 --- a/public/app/plugins/panel/geomap/layers/data/index.ts +++ b/public/app/plugins/panel/geomap/layers/data/index.ts @@ -8,4 +8,11 @@ import { dayNightLayer } from './dayNightLayer'; /** * Registry for layer handlers */ -export const dataLayers = [markersLayer, heatmapLayer, lastPointTracker, geojsonLayer, dayNightLayer, routeLayer]; +export const dataLayers = [ + markersLayer, + heatmapLayer, + lastPointTracker, + geojsonLayer, + dayNightLayer, + routeLayer, +]; diff --git a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx index ea4fbd94557..a7b0f9ee16b 100644 --- a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx @@ -10,16 +10,16 @@ import { import Map from 'ol/Map'; import { FeatureLike } from 'ol/Feature'; import { getLocationMatchers } from 'app/features/geo/utils/location'; -import { getScaledDimension, getColorDimension, getTextDimension, getScalarDimension } from 'app/features/dimensions'; import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper'; -import { MarkersLegend, MarkersLegendProps } from './MarkersLegend'; +import { MarkersLegend, MarkersLegendProps } from '../../components/MarkersLegend'; import { ReplaySubject } from 'rxjs'; -import { defaultStyleConfig, StyleConfig, StyleDimensions } from '../../style/types'; -import { StyleEditor } from './StyleEditor'; +import { defaultStyleConfig, StyleConfig } from '../../style/types'; +import { StyleEditor } from '../../editor/StyleEditor'; import { getStyleConfigState } from '../../style/utils'; import VectorLayer from 'ol/layer/Vector'; import { isNumber } from 'lodash'; import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource'; +import { getStyleDimension} from '../../utils/utils'; // Configuration options for Circle overlays export interface MarkersConfig { @@ -121,22 +121,7 @@ export const markersLayer: MapLayerRegistryItem = { } for (const frame of data.series) { - if (style.fields) { - const dims: StyleDimensions = {}; - if (style.fields.color) { - dims.color = getColorDimension(frame, style.config.color ?? defaultStyleConfig.color, theme); - } - if (style.fields.size) { - dims.size = getScaledDimension(frame, style.config.size ?? defaultStyleConfig.size); - } - if (style.fields.text) { - dims.text = getTextDimension(frame, style.config.text!); - } - if (style.fields.rotation) { - dims.rotation = getScalarDimension(frame, style.config.rotation ?? defaultStyleConfig.rotation); - } - style.dims = dims; - } + style.dims = getStyleDimension(frame, style, theme); // Post updates to the legend component if (legend) { diff --git a/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx b/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx index 23ad642e9f0..7bd573c1763 100644 --- a/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx @@ -17,7 +17,7 @@ import { Subscription, throttleTime } from 'rxjs'; import { getGeometryField, getLocationMatchers } from 'app/features/geo/utils/location'; import { getColorDimension } from 'app/features/dimensions'; import { defaultStyleConfig, StyleConfig, StyleDimensions } from '../../style/types'; -import { StyleEditor } from './StyleEditor'; +import { StyleEditor } from '../../editor/StyleEditor'; import { getStyleConfigState } from '../../style/utils'; import VectorLayer from 'ol/layer/Vector'; import { isNumber } from 'lodash'; diff --git a/public/app/plugins/panel/geomap/utils/utils.ts b/public/app/plugins/panel/geomap/utils/utils.ts new file mode 100644 index 00000000000..f3f373035c7 --- /dev/null +++ b/public/app/plugins/panel/geomap/utils/utils.ts @@ -0,0 +1,70 @@ +import { SelectableValue } from '@grafana/data'; +import { DataFrame, GrafanaTheme2 } from '@grafana/data/src'; +import { getColorDimension, getScalarDimension, getScaledDimension, getTextDimension } from 'app/features/dimensions'; +import { getGrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; + +import { defaultStyleConfig, StyleConfig, StyleConfigState, StyleDimensions } from '../style/types'; + +export function getStyleDimension( + frame: DataFrame | undefined, + style: StyleConfigState, + theme: GrafanaTheme2, + customStyleConfig?: StyleConfig +) { + const dims: StyleDimensions = {}; + if (customStyleConfig && Object.keys(customStyleConfig).length) { + dims.color = getColorDimension(frame, customStyleConfig.color ?? defaultStyleConfig.color, theme); + dims.size = getScaledDimension(frame, customStyleConfig.size ?? defaultStyleConfig.size); + dims.rotation = getScalarDimension(frame, customStyleConfig.rotation ?? defaultStyleConfig.rotation); + if (customStyleConfig.text && (customStyleConfig.text.field || customStyleConfig.text.fixed)) { + dims.text = getTextDimension(frame, customStyleConfig.text!); + } + } else { + if (style.fields) { + if (style.fields.color) { + dims.color = getColorDimension(frame, style.config.color ?? defaultStyleConfig.color, theme); + } + if (style.fields.size) { + dims.size = getScaledDimension(frame, style.config.size ?? defaultStyleConfig.size); + } + if (style.fields.text) { + dims.text = getTextDimension(frame, style.config.text!); + } + if (style.fields.rotation) { + dims.rotation = getScalarDimension(frame, style.config.rotation ?? defaultStyleConfig.rotation); + } + } + } + + return dims; +} + +let publicGeoJSONFiles: Array> | undefined = undefined; + +export function getPublicGeoJSONFiles(): Array> { + if (!publicGeoJSONFiles) { + publicGeoJSONFiles = []; + initGeojsonFiles(); // async + } + return publicGeoJSONFiles; +} + +// This will find all geojson files in the maps and gazetteer folders +async function initGeojsonFiles() { + const ds = await getGrafanaDatasource(); + for (let folder of ['maps', 'gazetteer']) { + ds.listFiles(folder).subscribe({ + next: (frame) => { + frame.forEach((item) => { + if (item.name.endsWith('.geojson')) { + const value = `public/${folder}/${item.name}`; + publicGeoJSONFiles!.push({ + value, + label: value, + }); + } + }); + }, + }); + } +}