GroupBy: add new groupby variable type and optional groupByKeys (#81717)

* add new groupby type

* rename to groupByKeys + introduce GroupByVariableModel

* fix unit test

* update scenes package

* update interface

* update fixture

* update unit test

* bump to scenes 2.6.2

* remove baseFilters for now
This commit is contained in:
Ashley Harrison 2024-02-07 11:14:04 +00:00 committed by GitHub
parent 5b9b990220
commit af8ea896d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 97 additions and 34 deletions

View File

@ -600,24 +600,24 @@ Configured template variables
A variable is a placeholder for a value. You can use variables in metric queries and in panel titles.
| Property | Type | Required | Default | Description |
|---------------|-------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | string | **Yes** | | Name of variable |
| `type` | string | **Yes** | | Dashboard variable type<br/>`query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.<br/>`adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).<br/>`constant`: Define a hidden constant.<br/>`datasource`: Quickly change the data source for an entire dashboard.<br/>`interval`: Interval variables represent time spans.<br/>`textbox`: Display a free text input field with an optional default value.<br/>`custom`: Define the variable options manually using a comma-separated list.<br/>`system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables<br/>Possible values are: `query`, `adhoc`, `constant`, `datasource`, `interval`, `textbox`, `custom`, `system`. |
| `allValue` | string | No | | Custom all value |
| `current` | [VariableOption](#variableoption) | No | | Option to be selected in a variable. |
| `datasource` | [DataSourceRef](#datasourceref) | No | | Ref to a DataSource instance |
| `description` | string | No | | Description of variable. It can be defined but `null`. |
| `hide` | integer | No | | Determine if the variable shows on dashboard<br/>Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).<br/>Possible values are: `0`, `1`, `2`. |
| `includeAll` | boolean | No | `false` | Whether all value option is available or not |
| `label` | string | No | | Optional display name |
| `multi` | boolean | No | `false` | Whether multiple values can be selected or not from variable value list |
| `options` | [VariableOption](#variableoption)[] | No | | Options that can be selected 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`. |
| `regex` | string | No | | Optional field, if you want to extract part of a series name or metric node segment.<br/>Named capture groups can be used to separate the display text and value. |
| `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/>`7`: Natural ASC<br/>`8`: Natural DESC<br/>Possible values are: `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`. |
| Property | Type | Required | Default | Description |
|---------------|-------------------------------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | string | **Yes** | | Name of variable |
| `type` | string | **Yes** | | Dashboard variable type<br/>`query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.<br/>`adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).<br/>`constant`: Define a hidden constant.<br/>`datasource`: Quickly change the data source for an entire dashboard.<br/>`interval`: Interval variables represent time spans.<br/>`textbox`: Display a free text input field with an optional default value.<br/>`custom`: Define the variable options manually using a comma-separated list.<br/>`system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables<br/>Possible values are: `query`, `adhoc`, `groupby`, `constant`, `datasource`, `interval`, `textbox`, `custom`, `system`. |
| `allValue` | string | No | | Custom all value |
| `current` | [VariableOption](#variableoption) | No | | Option to be selected in a variable. |
| `datasource` | [DataSourceRef](#datasourceref) | No | | Ref to a DataSource instance |
| `description` | string | No | | Description of variable. It can be defined but `null`. |
| `hide` | integer | No | | Determine if the variable shows on dashboard<br/>Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).<br/>Possible values are: `0`, `1`, `2`. |
| `includeAll` | boolean | No | `false` | Whether all value option is available or not |
| `label` | string | No | | Optional display name |
| `multi` | boolean | No | `false` | Whether multiple values can be selected or not from variable value list |
| `options` | [VariableOption](#variableoption)[] | No | | Options that can be selected 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`. |
| `regex` | string | No | | Optional field, if you want to extract part of a series name or metric node segment.<br/>Named capture groups can be used to separate the display text and value. |
| `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/>`7`: Natural ASC<br/>`8`: Natural DESC<br/>Possible values are: `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`. |
### VariableOption

View File

@ -291,7 +291,7 @@ lineage: schemas: [{
// `textbox`: Display a free text input field with an optional default value.
// `custom`: Define the variable options manually using a comma-separated list.
// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
#VariableType: "query" | "adhoc" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" @cuetsy(kind="type") @grafanamaturity(NeedsExpertReview)
#VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" @cuetsy(kind="type") @grafanamaturity(NeedsExpertReview)
// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
// Continuous color interpolates a color using the percentage of a value relative to min and max.

View File

@ -246,7 +246,7 @@
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "^2.4.0",
"@grafana/scenes": "2.6.3",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",

View File

@ -560,6 +560,7 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
/** Filters to dynamically apply to all queries */
filters?: AdHocVariableFilter[];
groupByKeys?: string[];
// Request Timing
startTime: number;

View File

@ -1,4 +1,5 @@
import { LoadingState } from './data';
import { MetricFindValue } from './datasource';
import { DataSourceRef } from './query';
export type VariableType = TypedVariableModel['type'];
@ -13,6 +14,7 @@ export interface VariableModel {
export type TypedVariableModel =
| QueryVariableModel
| AdHocVariableModel
| GroupByVariableModel
| ConstantVariableModel
| DataSourceVariableModel
| IntervalVariableModel
@ -64,6 +66,14 @@ export interface AdHocVariableModel extends BaseVariableModel {
baseFilters?: AdHocVariableFilter[];
}
export interface GroupByVariableModel extends BaseVariableModel {
type: 'groupby';
datasource: DataSourceRef | null;
groupByKeys: string[];
defaultOptions?: MetricFindValue[];
multi: true;
}
export interface VariableOption {
selected: boolean;
text: string | string[];

View File

@ -349,7 +349,7 @@ export type DashboardLinkType = ('link' | 'dashboards');
* `custom`: Define the variable options manually using a comma-separated list.
* `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
*/
export type VariableType = ('query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system');
export type VariableType = ('query' | 'adhoc' | 'groupby' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system');
/**
* Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.

View File

@ -159,6 +159,7 @@ const (
VariableTypeConstant VariableType = "constant"
VariableTypeCustom VariableType = "custom"
VariableTypeDatasource VariableType = "datasource"
VariableTypeGroupby VariableType = "groupby"
VariableTypeInterval VariableType = "interval"
VariableTypeQuery VariableType = "query"
VariableTypeSystem VariableType = "system"

View File

@ -0,0 +1,12 @@
import React from 'react';
import { GroupByVariable } from '@grafana/scenes';
interface GroupByVariableEditorProps {
variable: GroupByVariable;
onChange: (variable: GroupByVariable) => void;
}
export function GroupByVariableEditor(props: GroupByVariableEditorProps) {
return <div>GroupByVariableEditor</div>;
}

View File

@ -7,6 +7,7 @@ import {
QueryVariable,
DataSourceVariable,
AdHocFiltersVariable,
GroupByVariable,
TextBoxVariable,
SceneVariableSet,
} from '@grafana/scenes';
@ -18,6 +19,7 @@ import { AdHocFiltersVariableEditor } from './editors/AdHocFiltersVariableEditor
import { ConstantVariableEditor } from './editors/ConstantVariableEditor';
import { CustomVariableEditor } from './editors/CustomVariableEditor';
import { DataSourceVariableEditor } from './editors/DataSourceVariableEditor';
import { GroupByVariableEditor } from './editors/GroupByVariableEditor';
import { IntervalVariableEditor } from './editors/IntervalVariableEditor';
import { QueryVariableEditor } from './editors/QueryVariableEditor';
import { TextBoxVariableEditor } from './editors/TextBoxVariableEditor';
@ -73,7 +75,16 @@ jest.mock('@grafana/runtime', () => ({
describe('isEditableVariableType', () => {
it('should return true for editable variable types', () => {
const editableTypes: VariableType[] = ['custom', 'query', 'constant', 'interval', 'datasource', 'adhoc', 'textbox'];
const editableTypes: VariableType[] = [
'custom',
'query',
'constant',
'interval',
'datasource',
'adhoc',
'groupby',
'textbox',
];
editableTypes.forEach((type) => {
expect(isEditableVariableType(type)).toBe(true);
});
@ -99,7 +110,7 @@ describe('getVariableTypeSelectOptions', () => {
it('should return an array of selectable values for editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(7);
expect(options).toHaveLength(8);
options.forEach((option, index) => {
const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index];
@ -132,6 +143,7 @@ describe('getVariableEditor', () => {
['interval', IntervalVariableEditor],
['datasource', DataSourceVariableEditor],
['adhoc', AdHocFiltersVariableEditor],
['groupby', GroupByVariableEditor],
['textbox', TextBoxVariableEditor],
])('should return the correct editor for variable type "%s"', (type, ExpectedVariableEditor) => {
expect(getVariableEditor(type as EditableVariableType)).toBe(ExpectedVariableEditor);
@ -158,6 +170,7 @@ describe('getVariableScene', () => {
['interval', IntervalVariable],
['datasource', DataSourceVariable],
['adhoc', AdHocFiltersVariable],
['groupby', GroupByVariable],
['textbox', TextBoxVariable],
])('should return the scene variable instance for the given editable variable type', () => {
const initialState = { name: 'MyVariable' };

View File

@ -10,6 +10,7 @@ import {
TextBoxVariable,
QueryVariable,
AdHocFilterSet,
GroupByVariable,
SceneVariable,
MultiValueVariable,
SceneVariableState,
@ -22,6 +23,7 @@ import { AdHocFiltersVariableEditor } from './editors/AdHocFiltersVariableEditor
import { ConstantVariableEditor } from './editors/ConstantVariableEditor';
import { CustomVariableEditor } from './editors/CustomVariableEditor';
import { DataSourceVariableEditor } from './editors/DataSourceVariableEditor';
import { GroupByVariableEditor } from './editors/GroupByVariableEditor';
import { IntervalVariableEditor } from './editors/IntervalVariableEditor';
import { QueryVariableEditor } from './editors/QueryVariableEditor';
import { TextBoxVariableEditor } from './editors/TextBoxVariableEditor';
@ -69,6 +71,11 @@ export const EDITABLE_VARIABLES: Record<EditableVariableType, EditableVariableCo
description: 'Add key/value filters on the fly',
editor: AdHocFiltersVariableEditor,
},
groupby: {
name: 'Group by',
description: 'Add keys to group by on the fly',
editor: GroupByVariableEditor,
},
textbox: {
name: 'Textbox',
description: 'Define a textbox variable, where users can enter any arbitrary string',
@ -84,6 +91,7 @@ export const EDITABLE_VARIABLES_SELECT_ORDER: EditableVariableType[] = [
'datasource',
'interval',
'adhoc',
'groupby',
];
export function getVariableTypeSelectOptions(): Array<SelectableValue<EditableVariableType>> {
@ -118,6 +126,8 @@ export function getVariableScene(type: EditableVariableType, initialState: Commo
case 'adhoc':
// TODO: Initialize properly AdHocFilterSet with initialState
return new AdHocFilterSet({ name: initialState.name });
case 'groupby':
return new GroupByVariable(initialState);
case 'textbox':
return new TextBoxVariable(initialState);
}

View File

@ -19,6 +19,7 @@ import {
createCustomVariable,
createDashboardVariable,
createDatasourceVariable,
createGroupByVariable,
createIntervalVariable,
createOrgVariable,
createQueryVariable,
@ -164,6 +165,7 @@ describe('type guards', () => {
const variableFactsObj: Record<VariableType | ExtraVariableTypes, VariableFacts> = {
query: { variable: createQueryVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
adhoc: { variable: createAdhocVariable(), isMulti: false, hasOptions: false, hasCurrent: false },
groupby: { variable: createGroupByVariable(), isMulti: true, hasOptions: false, hasCurrent: false },
constant: { variable: createConstantVariable(), isMulti: false, hasOptions: true, hasCurrent: true },
datasource: { variable: createDatasourceVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
interval: { variable: createIntervalVariable(), isMulti: false, hasOptions: true, hasCurrent: true },

View File

@ -5,6 +5,7 @@ import {
CustomVariableModel,
DashboardVariableModel,
DataSourceVariableModel,
GroupByVariableModel,
IntervalVariableModel,
LoadingState,
OrgVariableModel,
@ -78,6 +79,19 @@ export function createAdhocVariable(input?: Partial<AdHocVariableModel>): AdHocV
};
}
export function createGroupByVariable(input?: Partial<GroupByVariableModel>): GroupByVariableModel {
return {
...createBaseVariableModel('groupby'),
datasource: {
uid: 'abc-123',
type: 'prometheus',
},
groupByKeys: [],
multi: true,
...input,
};
}
export function createConstantVariable(input: Partial<ConstantVariableModel> = {}): ConstantVariableModel {
return {
...createBaseVariableModel('constant'),

View File

@ -3850,7 +3850,7 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/scenes@npm:^2.4.0":
"@grafana/scenes@npm:2.6.3":
version: 2.6.3
resolution: "@grafana/scenes@npm:2.6.3"
dependencies:
@ -16834,12 +16834,12 @@ __metadata:
linkType: hard
"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.0":
version: 1.15.5
resolution: "follow-redirects@npm:1.15.5"
version: 1.15.3
resolution: "follow-redirects@npm:1.15.3"
peerDependenciesMeta:
debug:
optional: true
checksum: 10/d467f13c1c6aa734599b8b369cd7a625b20081af358f6204ff515f6f4116eb440de9c4e0c49f10798eeb0df26c95dd05d5e0d9ddc5786ab1a8a8abefe92929b4
checksum: 10/60d98693f4976892f8c654b16ef6d1803887a951898857ab0cdc009570b1c06314ad499505b7a040ac5b98144939f8597766e5e6a6859c0945d157b473aa6f5f
languageName: node
linkType: hard
@ -17772,7 +17772,7 @@ __metadata:
"@grafana/o11y-ds-frontend": "workspace:*"
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/scenes": "npm:^2.4.0"
"@grafana/scenes": "npm:2.6.3"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/tsconfig": "npm:^1.3.0-rc1"
@ -25579,11 +25579,11 @@ __metadata:
linkType: hard
"react-hook-form@npm:^7.49.2":
version: 7.50.1
resolution: "react-hook-form@npm:7.50.1"
version: 7.50.0
resolution: "react-hook-form@npm:7.50.0"
peerDependencies:
react: ^16.8.0 || ^17 || ^18
checksum: 10/54a9daa2143c601a9867e96a2159a0bbe98707b5bbeb5953bfdf3342d2f04bfcaa6169907ed167c5c1f3a044630860d4f43685f4ac4e15b9cd892d1b00d54dde
checksum: 10/3b85cc179053af72a2734f2e77767de8f9b3ecbefeee282b73e81141c4b7bb97308ec00da61fdc25a28299a2defb74bff66417bb85a66357f5ceddba7b697ae7
languageName: node
linkType: hard
@ -26243,12 +26243,12 @@ __metadata:
linkType: hard
"react-zoom-pan-pinch@npm:^3.3.0":
version: 3.4.2
resolution: "react-zoom-pan-pinch@npm:3.4.2"
version: 3.4.1
resolution: "react-zoom-pan-pinch@npm:3.4.1"
peerDependencies:
react: "*"
react-dom: "*"
checksum: 10/3014c26523d69eb6a10fb1862374e4fcfb8bcf14614b0a872ca178473634427753d5c7ed6f80233f973236fcd232dd5eec70365c6eb68703e435a9a74b75bf3f
checksum: 10/6d46d88c869d49e511ed6899d736c8952a56ec38f7cdfa47fdd6c819c265ae4c503d4bdf6cb85557efb2e259489db6baaac9a3f3e0fa8703a534605afca6f8c2
languageName: node
linkType: hard