From 860b3bbce7c307b0b1042a8407114a843dcc27ba Mon Sep 17 00:00:00 2001 From: Ivan Babrou Date: Tue, 21 Nov 2023 01:17:38 -0800 Subject: [PATCH] Dashboards: Implement natural sort for query variables (#78024) --- .../kinds/core/dashboard/schema-reference.md | 2 +- kinds/dashboard/dashboard_kind.cue | 4 +++- packages/grafana-data/src/types/templateVars.ts | 2 ++ .../src/raw/dashboard/x/dashboard_types.gen.ts | 4 ++++ pkg/kinds/dashboard/dashboard_spec_gen.go | 6 ++++++ .../variables/query/QueryVariableSortSelect.tsx | 2 ++ .../app/features/variables/query/reducer.test.ts | 15 +++++++++++++++ public/app/features/variables/query/reducer.ts | 12 ++++++++++++ 8 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/sources/developers/kinds/core/dashboard/schema-reference.md b/docs/sources/developers/kinds/core/dashboard/schema-reference.md index 4b6f67f6c34..b3773b104ab 100644 --- a/docs/sources/developers/kinds/core/dashboard/schema-reference.md +++ b/docs/sources/developers/kinds/core/dashboard/schema-reference.md @@ -648,7 +648,7 @@ A variable is a placeholder for a value. You can use variables in metric queries | `query` | | No | | Query used to fetch values for a variable | | `refresh` | integer | No | | Options to config when to refresh a variable
`0`: Never refresh the variable
`1`: Queries the data source every time the dashboard loads.
`2`: Queries the data source when the dashboard time range changes.
Possible values are: `0`, `1`, `2`. | | `skipUrlSync` | boolean | No | `false` | Whether the variable value should be managed by URL query params or not | -| `sort` | integer | No | | Sort variable options
Accepted values are:
`0`: No sorting
`1`: Alphabetical ASC
`2`: Alphabetical DESC
`3`: Numerical ASC
`4`: Numerical DESC
`5`: Alphabetical Case Insensitive ASC
`6`: Alphabetical Case Insensitive DESC
Possible values are: `0`, `1`, `2`, `3`, `4`, `5`, `6`. | +| `sort` | integer | No | | Sort variable options
Accepted values are:
`0`: No sorting
`1`: Alphabetical ASC
`2`: Alphabetical DESC
`3`: Numerical ASC
`4`: Numerical DESC
`5`: Alphabetical Case Insensitive ASC
`6`: Alphabetical Case Insensitive DESC
`7`: Natural ASC
`8`: Natural DESC
Possible values are: `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`. | ### VariableOption diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 5fb6347ee3d..4964b029a54 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -243,7 +243,9 @@ lineage: schemas: [{ // `4`: Numerical DESC // `5`: Alphabetical Case Insensitive ASC // `6`: Alphabetical Case Insensitive DESC - #VariableSort: 0 | 1 | 2 | 3 | 4 | 5 | 6 @cuetsy(kind="enum",memberNames="disabled|alphabeticalAsc|alphabeticalDesc|numericalAsc|numericalDesc|alphabeticalCaseInsensitiveAsc|alphabeticalCaseInsensitiveDesc") + // `7`: Natural ASC + // `8`: Natural DESC + #VariableSort: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 @cuetsy(kind="enum",memberNames="disabled|alphabeticalAsc|alphabeticalDesc|numericalAsc|numericalDesc|alphabeticalCaseInsensitiveAsc|alphabeticalCaseInsensitiveDesc|naturalAsc|naturalDesc") // Ref to a DataSource instance #DataSourceRef: { diff --git a/packages/grafana-data/src/types/templateVars.ts b/packages/grafana-data/src/types/templateVars.ts index d16daed9ea4..86d5c50594d 100644 --- a/packages/grafana-data/src/types/templateVars.ts +++ b/packages/grafana-data/src/types/templateVars.ts @@ -36,6 +36,8 @@ export enum VariableSort { numericalDesc, alphabeticalCaseInsensitiveAsc, alphabeticalCaseInsensitiveDesc, + naturalAsc, + naturalDesc, } export enum VariableHide { diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index 666357753f5..b2e807b1361 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -233,6 +233,8 @@ export enum VariableHide { * `4`: Numerical DESC * `5`: Alphabetical Case Insensitive ASC * `6`: Alphabetical Case Insensitive DESC + * `7`: Natural ASC + * `8`: Natural DESC */ export enum VariableSort { alphabeticalAsc = 1, @@ -240,6 +242,8 @@ export enum VariableSort { alphabeticalCaseInsensitiveDesc = 6, alphabeticalDesc = 2, disabled = 0, + naturalAsc = 7, + naturalDesc = 8, numericalAsc = 3, numericalDesc = 4, } diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index 7e2a86472dd..072eaf552fb 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -152,6 +152,8 @@ const ( VariableSortN4 VariableSort = 4 VariableSortN5 VariableSort = 5 VariableSortN6 VariableSort = 6 + VariableSortN7 VariableSort = 7 + VariableSortN8 VariableSort = 8 ) // Defines values for VariableType. @@ -960,6 +962,8 @@ type VariableModel struct { // `4`: Numerical DESC // `5`: Alphabetical Case Insensitive ASC // `6`: Alphabetical Case Insensitive DESC + // `7`: Natural ASC + // `8`: Natural DESC Sort *VariableSort `json:"sort,omitempty"` // Dashboard variable type @@ -1001,6 +1005,8 @@ type VariableRefresh int // `4`: Numerical DESC // `5`: Alphabetical Case Insensitive ASC // `6`: Alphabetical Case Insensitive DESC +// `7`: Natural ASC +// `8`: Natural DESC type VariableSort int // Dashboard variable type diff --git a/public/app/features/variables/query/QueryVariableSortSelect.tsx b/public/app/features/variables/query/QueryVariableSortSelect.tsx index 33e8f116770..c6d0bc38cdb 100644 --- a/public/app/features/variables/query/QueryVariableSortSelect.tsx +++ b/public/app/features/variables/query/QueryVariableSortSelect.tsx @@ -19,6 +19,8 @@ const SORT_OPTIONS = [ { label: 'Numerical (desc)', value: VariableSort.numericalDesc }, { label: 'Alphabetical (case-insensitive, asc)', value: VariableSort.alphabeticalCaseInsensitiveAsc }, { label: 'Alphabetical (case-insensitive, desc)', value: VariableSort.alphabeticalCaseInsensitiveDesc }, + { label: 'Natural (asc)', value: VariableSort.naturalAsc }, + { label: 'Natural (desc)', value: VariableSort.naturalDesc }, ]; export function QueryVariableSortSelect({ onChange, sort }: PropsWithChildren) { diff --git a/public/app/features/variables/query/reducer.test.ts b/public/app/features/variables/query/reducer.test.ts index 89643ff82da..e0f273484bd 100644 --- a/public/app/features/variables/query/reducer.test.ts +++ b/public/app/features/variables/query/reducer.test.ts @@ -277,6 +277,8 @@ describe('sortVariableValues', () => { ${[{ text: '1' }, { text: null }, { text: '2' }]} | ${VariableSort.numericalDesc} | ${[{ text: '2' }, { text: '1' }, { text: null }]} ${[{ text: 'a' }, { text: null }, { text: 'b' }]} | ${VariableSort.alphabeticalCaseInsensitiveAsc} | ${[{ text: null }, { text: 'a' }, { text: 'b' }]} ${[{ text: 'a' }, { text: null }, { text: 'b' }]} | ${VariableSort.alphabeticalCaseInsensitiveDesc} | ${[{ text: 'b' }, { text: 'a' }, { text: null }]} + ${[{ text: '1' }, { text: null }, { text: '2' }]} | ${VariableSort.naturalAsc} | ${[{ text: null }, { text: '1' }, { text: '2' }]} + ${[{ text: '1' }, { text: null }, { text: '2' }]} | ${VariableSort.naturalDesc} | ${[{ text: '2' }, { text: '1' }, { text: null }]} `( 'then it should sort the options correctly without throwing (sortOrder:$sortOrder)', ({ options, sortOrder, expected }) => { @@ -286,6 +288,19 @@ describe('sortVariableValues', () => { } ); }); + + describe('when using natural sort', () => { + it.each` + options | sortOrder | expected + ${[{ text: '12-lax01' }, { text: '4-sjc01' }, { text: '21-lhr01' }]} | ${VariableSort.naturalAsc} | ${[{ text: '4-sjc01' }, { text: '12-lax01' }, { text: '21-lhr01' }]} + ${[{ text: 'lax01' }, { text: 'sjc01' }, { text: 'sjc02' }, { text: 'lhr01' }]} | ${VariableSort.naturalAsc} | ${[{ text: 'lax01' }, { text: 'lhr01' }, { text: 'sjc01' }, { text: 'sjc02' }]} + ${[{ text: '4m10' }, { text: '4m2' }, { text: '4m1' }, { text: '4m4' }]} | ${VariableSort.naturalAsc} | ${[{ text: '4m1' }, { text: '4m2' }, { text: '4m4' }, { text: '4m10' }]} + `('then it should sort like humans would naturally sort', ({ options, sortOrder, expected }) => { + const result = sortVariableValues(options, sortOrder); + + expect(result).toEqual(expected); + }); + }); }); describe('metricNamesToVariableValues', () => { diff --git a/public/app/features/variables/query/reducer.ts b/public/app/features/variables/query/reducer.ts index b59354013b6..a5c623c6699 100644 --- a/public/app/features/variables/query/reducer.ts +++ b/public/app/features/variables/query/reducer.ts @@ -56,6 +56,18 @@ export const sortVariableValues = (options: any[], sortOrder: VariableSort) => { options = sortBy(options, (opt) => { return toLower(opt.text); }); + } else if (sortType === 4) { + options.sort((a, b) => { + if (!a.text) { + return -1; + } + + if (!b.text) { + return 1; + } + + return a.text.localeCompare(b.text, undefined, { numeric: true }); + }); } if (reverseSort) {