Dashboards: Implement natural sort for query variables (#78024)

pull/78445/head
Ivan Babrou 2 years ago committed by GitHub
parent b2d94f3d85
commit 860b3bbce7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docs/sources/developers/kinds/core/dashboard/schema-reference.md
  2. 4
      kinds/dashboard/dashboard_kind.cue
  3. 2
      packages/grafana-data/src/types/templateVars.ts
  4. 4
      packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts
  5. 6
      pkg/kinds/dashboard/dashboard_spec_gen.go
  6. 2
      public/app/features/variables/query/QueryVariableSortSelect.tsx
  7. 15
      public/app/features/variables/query/reducer.test.ts
  8. 12
      public/app/features/variables/query/reducer.ts

@ -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 | | `query` | | No | | Query used to fetch values for a variable |
| `refresh` | integer | No | | Options to config when to refresh a variable<br/>`0`: Never refresh the variable<br/>`1`: Queries the data source every time the dashboard loads.<br/>`2`: Queries the data source when the dashboard time range changes.<br/>Possible values are: `0`, `1`, `2`. | | `refresh` | integer | No | | Options to config when to refresh a variable<br/>`0`: Never refresh the variable<br/>`1`: Queries the data source every time the dashboard loads.<br/>`2`: Queries the data source when the dashboard time range changes.<br/>Possible values are: `0`, `1`, `2`. |
| `skipUrlSync` | boolean | No | `false` | Whether the variable value should be managed by URL query params or not | | `skipUrlSync` | boolean | No | `false` | Whether the variable value should be managed by URL query params or not |
| `sort` | integer | No | | Sort variable options<br/>Accepted values are:<br/>`0`: No sorting<br/>`1`: Alphabetical ASC<br/>`2`: Alphabetical DESC<br/>`3`: Numerical ASC<br/>`4`: Numerical DESC<br/>`5`: Alphabetical Case Insensitive ASC<br/>`6`: Alphabetical Case Insensitive DESC<br/>Possible values are: `0`, `1`, `2`, `3`, `4`, `5`, `6`. | | `sort` | integer | No | | Sort variable options<br/>Accepted values are:<br/>`0`: No sorting<br/>`1`: Alphabetical ASC<br/>`2`: Alphabetical DESC<br/>`3`: Numerical ASC<br/>`4`: Numerical DESC<br/>`5`: Alphabetical Case Insensitive ASC<br/>`6`: Alphabetical Case Insensitive DESC<br/>`7`: Natural ASC<br/>`8`: Natural DESC<br/>Possible values are: `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`. |
### VariableOption ### VariableOption

@ -243,7 +243,9 @@ lineage: schemas: [{
// `4`: Numerical DESC // `4`: Numerical DESC
// `5`: Alphabetical Case Insensitive ASC // `5`: Alphabetical Case Insensitive ASC
// `6`: Alphabetical Case Insensitive DESC // `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 // Ref to a DataSource instance
#DataSourceRef: { #DataSourceRef: {

@ -36,6 +36,8 @@ export enum VariableSort {
numericalDesc, numericalDesc,
alphabeticalCaseInsensitiveAsc, alphabeticalCaseInsensitiveAsc,
alphabeticalCaseInsensitiveDesc, alphabeticalCaseInsensitiveDesc,
naturalAsc,
naturalDesc,
} }
export enum VariableHide { export enum VariableHide {

@ -233,6 +233,8 @@ export enum VariableHide {
* `4`: Numerical DESC * `4`: Numerical DESC
* `5`: Alphabetical Case Insensitive ASC * `5`: Alphabetical Case Insensitive ASC
* `6`: Alphabetical Case Insensitive DESC * `6`: Alphabetical Case Insensitive DESC
* `7`: Natural ASC
* `8`: Natural DESC
*/ */
export enum VariableSort { export enum VariableSort {
alphabeticalAsc = 1, alphabeticalAsc = 1,
@ -240,6 +242,8 @@ export enum VariableSort {
alphabeticalCaseInsensitiveDesc = 6, alphabeticalCaseInsensitiveDesc = 6,
alphabeticalDesc = 2, alphabeticalDesc = 2,
disabled = 0, disabled = 0,
naturalAsc = 7,
naturalDesc = 8,
numericalAsc = 3, numericalAsc = 3,
numericalDesc = 4, numericalDesc = 4,
} }

@ -152,6 +152,8 @@ const (
VariableSortN4 VariableSort = 4 VariableSortN4 VariableSort = 4
VariableSortN5 VariableSort = 5 VariableSortN5 VariableSort = 5
VariableSortN6 VariableSort = 6 VariableSortN6 VariableSort = 6
VariableSortN7 VariableSort = 7
VariableSortN8 VariableSort = 8
) )
// Defines values for VariableType. // Defines values for VariableType.
@ -960,6 +962,8 @@ type VariableModel struct {
// `4`: Numerical DESC // `4`: Numerical DESC
// `5`: Alphabetical Case Insensitive ASC // `5`: Alphabetical Case Insensitive ASC
// `6`: Alphabetical Case Insensitive DESC // `6`: Alphabetical Case Insensitive DESC
// `7`: Natural ASC
// `8`: Natural DESC
Sort *VariableSort `json:"sort,omitempty"` Sort *VariableSort `json:"sort,omitempty"`
// Dashboard variable type // Dashboard variable type
@ -1001,6 +1005,8 @@ type VariableRefresh int
// `4`: Numerical DESC // `4`: Numerical DESC
// `5`: Alphabetical Case Insensitive ASC // `5`: Alphabetical Case Insensitive ASC
// `6`: Alphabetical Case Insensitive DESC // `6`: Alphabetical Case Insensitive DESC
// `7`: Natural ASC
// `8`: Natural DESC
type VariableSort int type VariableSort int
// Dashboard variable type // Dashboard variable type

@ -19,6 +19,8 @@ const SORT_OPTIONS = [
{ label: 'Numerical (desc)', value: VariableSort.numericalDesc }, { label: 'Numerical (desc)', value: VariableSort.numericalDesc },
{ label: 'Alphabetical (case-insensitive, asc)', value: VariableSort.alphabeticalCaseInsensitiveAsc }, { label: 'Alphabetical (case-insensitive, asc)', value: VariableSort.alphabeticalCaseInsensitiveAsc },
{ label: 'Alphabetical (case-insensitive, desc)', value: VariableSort.alphabeticalCaseInsensitiveDesc }, { 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<Props>) { export function QueryVariableSortSelect({ onChange, sort }: PropsWithChildren<Props>) {

@ -277,6 +277,8 @@ describe('sortVariableValues', () => {
${[{ text: '1' }, { text: null }, { text: '2' }]} | ${VariableSort.numericalDesc} | ${[{ text: '2' }, { text: '1' }, { text: null }]} ${[{ 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.alphabeticalCaseInsensitiveAsc} | ${[{ text: null }, { text: 'a' }, { text: 'b' }]}
${[{ text: 'a' }, { text: null }, { text: 'b' }]} | ${VariableSort.alphabeticalCaseInsensitiveDesc} | ${[{ text: 'b' }, { text: 'a' }, { text: null }]} ${[{ 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)', 'then it should sort the options correctly without throwing (sortOrder:$sortOrder)',
({ options, sortOrder, expected }) => { ({ 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', () => { describe('metricNamesToVariableValues', () => {

@ -56,6 +56,18 @@ export const sortVariableValues = (options: any[], sortOrder: VariableSort) => {
options = sortBy(options, (opt) => { options = sortBy(options, (opt) => {
return toLower(opt.text); 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) { if (reverseSort) {

Loading…
Cancel
Save