diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 0b4e525f30c..abdfdfce1b3 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -16,6 +16,7 @@ export * from './events'; export * from './themes'; export * from './monaco'; export * from './geo/layer'; +export * from './query'; export { type ValueMatcherOptions, type BasicValueMatcherOptions, diff --git a/packages/grafana-data/src/query/index.ts b/packages/grafana-data/src/query/index.ts new file mode 100644 index 00000000000..b01ae2451d5 --- /dev/null +++ b/packages/grafana-data/src/query/index.ts @@ -0,0 +1 @@ +export * from './refId'; diff --git a/packages/grafana-data/src/query/refId.test.ts b/packages/grafana-data/src/query/refId.test.ts new file mode 100644 index 00000000000..c818f6156cc --- /dev/null +++ b/packages/grafana-data/src/query/refId.test.ts @@ -0,0 +1,35 @@ +import { DataQuery } from '@grafana/schema'; + +import { getNextRefId } from '.'; + +export interface TestQuery extends DataQuery { + name?: string; +} + +function dataQueryHelper(ids: string[]): DataQuery[] { + return ids.map((letter) => { + return { refId: letter }; + }); +} + +const singleDataQuery: DataQuery[] = dataQueryHelper('ABCDE'.split('')); +const outOfOrderDataQuery: DataQuery[] = dataQueryHelper('ABD'.split('')); +const singleExtendedDataQuery: DataQuery[] = dataQueryHelper('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')); + +describe('Get next refId char', () => { + it('should return next char', () => { + expect(getNextRefId(singleDataQuery)).toEqual('F'); + }); + + it('should get first char', () => { + expect(getNextRefId([])).toEqual('A'); + }); + + it('should get the first available character if a query has been deleted out of order', () => { + expect(getNextRefId(outOfOrderDataQuery)).toEqual('C'); + }); + + it('should append a new char and start from AA when Z is reached', () => { + expect(getNextRefId(singleExtendedDataQuery)).toEqual('AA'); + }); +}); diff --git a/packages/grafana-data/src/query/refId.ts b/packages/grafana-data/src/query/refId.ts new file mode 100644 index 00000000000..bdfa87ef68a --- /dev/null +++ b/packages/grafana-data/src/query/refId.ts @@ -0,0 +1,23 @@ +import { DataQuery } from '@grafana/schema'; + +/** + * Finds the next available refId for a query + */ +export const getNextRefId = (queries: DataQuery[]): string => { + for (let num = 0; ; num++) { + const refId = getRefId(num); + if (!queries.some((query) => query.refId === refId)) { + return refId; + } + } +}; + +function getRefId(num: number): string { + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + if (num < letters.length) { + return letters[num]; + } else { + return getRefId(Math.floor(num / letters.length) - 1) + letters[num % letters.length]; + } +} diff --git a/public/app/core/utils/query.ts b/public/app/core/utils/query.ts index bd5cdefe808..ef75c918ce1 100644 --- a/public/app/core/utils/query.ts +++ b/public/app/core/utils/query.ts @@ -1,5 +1,6 @@ import { DataQuery, DataSourceRef } from '@grafana/data'; +// @deprecated use the `getNextRefId` function from grafana/data instead export const getNextRefIdChar = (queries: DataQuery[]): string => { for (let num = 0; ; num++) { const refId = getRefId(num); diff --git a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts index 01c532ab939..03da65ed526 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts @@ -2,8 +2,8 @@ // Migrations applied by the DashboardMigrator are performed before the plugin is loaded. // DashboardMigrator migrations are tied to a certain minimum version of a dashboard which means they will only be ran once. -import { DataQuery, AnnotationQuery } from '@grafana/data'; -import { getNextRefIdChar } from 'app/core/utils/query'; +import { AnnotationQuery, getNextRefId } from '@grafana/data'; +import { DataQuery } from '@grafana/schema'; import { CloudWatchMetricsQuery, LegacyAnnotationQuery, MetricQueryType, MetricEditorMode } from '../types'; @@ -20,7 +20,7 @@ export function migrateMultipleStatsMetricsQuery( } } for (const newTarget of newQueries) { - newTarget.refId = getNextRefIdChar(panelQueries); + newTarget.refId = getNextRefId(panelQueries); delete newTarget.statistics; panelQueries.push(newTarget); }