mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
Annotations: Support filtering the target panels (#66325)
Co-authored-by: Adela Almasan <adela.almasan@grafana.com> Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
parent
a384194e15
commit
9452c0d718
@ -858,7 +858,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "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.", "4"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "5"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "6"]
|
||||
],
|
||||
"packages/grafana-toolkit/src/cli/tasks/component.create.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
@ -2231,7 +2233,10 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
|
||||
],
|
||||
"public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"]
|
||||
],
|
||||
"public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
433
devenv/dev-dashboards/annotations/annotation-filtering.json
Normal file
433
devenv/dev-dashboards/annotations/annotation-filtering.json
Normal file
@ -0,0 +1,433 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"enable": true,
|
||||
"filter": {
|
||||
"exclude": false,
|
||||
"ids": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"iconColor": "red",
|
||||
"name": "Red, only panel 1",
|
||||
"target": {
|
||||
"lines": 4,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations"
|
||||
}
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"enable": true,
|
||||
"filter": {
|
||||
"exclude": true,
|
||||
"ids": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"iconColor": "yellow",
|
||||
"name": "Yellow - all except 1",
|
||||
"target": {
|
||||
"lines": 5,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations"
|
||||
}
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"enable": true,
|
||||
"filter": {
|
||||
"exclude": false,
|
||||
"ids": [
|
||||
3,
|
||||
4
|
||||
]
|
||||
},
|
||||
"iconColor": "dark-purple",
|
||||
"name": "Purple only panel 3+4",
|
||||
"target": {
|
||||
"lines": 6,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 119,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"title": "Panel one",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"title": "Panel two",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 3,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"title": "Panel three",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"title": "Panel four",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "annotations"],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-30m",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Annotation filtering",
|
||||
"uid": "ed155665",
|
||||
"version": 17,
|
||||
"weekStart": ""
|
||||
}
|
@ -868,4 +868,4 @@
|
||||
"uid": "e7c29343-6d1e-4167-9c13-803fe5be8c46",
|
||||
"version": 48,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
@ -119,8 +119,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@ -582,4 +581,4 @@
|
||||
"uid": "cdd412c4",
|
||||
"version": 6,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
@ -453,4 +453,4 @@
|
||||
"uid": "ZqZnVvFZz",
|
||||
"version": 8,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
@ -1040,4 +1040,4 @@
|
||||
"uid": "U_bZIMRMk",
|
||||
"version": 7,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,13 @@ local dashboard = grafana.dashboard;
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
dashboard.new('annotation-filtering', import '../dev-dashboards/annotations/annotation-filtering.json') +
|
||||
resource.addMetadata('folder', 'dev-dashboards') +
|
||||
{
|
||||
spec+: {
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
dashboard.new('auto_decimals', import '../dev-dashboards/panel-common/auto_decimals.json') +
|
||||
resource.addMetadata('folder', 'dev-dashboards') +
|
||||
{
|
||||
|
@ -13,32 +13,84 @@ title: Dashboard kind
|
||||
|
||||
A Grafana dashboard.
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|------------------------|-----------------------------------|----------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `editable` | boolean | **Yes** | `true` | Whether a dashboard is editable or not. |
|
||||
| `graphTooltip` | integer | **Yes** | `0` | 0 for no shared crosshair or tooltip (default).<br/>1 for shared crosshair.<br/>2 for shared crosshair AND shared tooltip.<br/>Possible values are: `0`, `1`, `2`. |
|
||||
| `schemaVersion` | uint16 | **Yes** | `36` | Version of the JSON schema, incremented each time a Grafana update brings<br/>changes to said schema.<br/>TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion |
|
||||
| `style` | string | **Yes** | `dark` | Theme of dashboard.<br/>Possible values are: `dark`, `light`. |
|
||||
| `annotations` | [object](#annotations) | No | | TODO docs |
|
||||
| `description` | string | No | | Description of dashboard. |
|
||||
| `fiscalYearStartMonth` | integer | No | `0` | The month that the fiscal year starts on. 0 = January, 11 = December<br/>Constraint: `>=0 & <12`. |
|
||||
| `gnetId` | string | No | | For dashboards imported from the https://grafana.com/grafana/dashboards/ portal |
|
||||
| `id` | integer | No | | Unique numeric identifier for the dashboard.<br/>TODO must isolate or remove identifiers local to a Grafana instance...? |
|
||||
| `links` | [DashboardLink](#dashboardlink)[] | No | | TODO docs |
|
||||
| `liveNow` | boolean | No | | When set to true, the dashboard will redraw panels at an interval matching the pixel width.<br/>This will keep data "moving left" regardless of the query refresh rate. This setting helps<br/>avoid dashboards presenting stale live data |
|
||||
| `panels` | [object](#panels)[] | No | | |
|
||||
| `refresh` | | No | | Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". |
|
||||
| `revision` | integer | No | | This property should only be used in dashboards defined by plugins. It is a quick check<br/>to see if the version has changed since the last time. Unclear why using the version property<br/>is insufficient. |
|
||||
| `snapshot` | [Snapshot](#snapshot) | No | | TODO docs |
|
||||
| `tags` | string[] | No | | Tags associated with dashboard. |
|
||||
| `templating` | [object](#templating) | No | | TODO docs |
|
||||
| `time` | [object](#time) | No | | Time range for dashboard, e.g. last 6 hours, last 7 days, etc |
|
||||
| `timepicker` | [object](#timepicker) | No | | TODO docs<br/>TODO this appears to be spread all over in the frontend. Concepts will likely need tidying in tandem with schema changes |
|
||||
| `timezone` | string | No | `browser` | Timezone of dashboard. Accepts IANA TZDB zone ID or "browser" or "utc". |
|
||||
| `title` | string | No | | Title of dashboard. |
|
||||
| `uid` | string | No | | Unique dashboard identifier that can be generated by anyone. string (8-40) |
|
||||
| `version` | uint32 | No | | Version of the dashboard, incremented each time the dashboard is updated. |
|
||||
| `weekStart` | string | No | | TODO docs |
|
||||
| Property | Type | Required | Default | Description |
|
||||
|------------------------|---------------------------------------------|----------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `editable` | boolean | **Yes** | `true` | Whether a dashboard is editable or not. |
|
||||
| `graphTooltip` | integer | **Yes** | `0` | 0 for no shared crosshair or tooltip (default).<br/>1 for shared crosshair.<br/>2 for shared crosshair AND shared tooltip.<br/>Possible values are: `0`, `1`, `2`. |
|
||||
| `schemaVersion` | uint16 | **Yes** | `36` | Version of the JSON schema, incremented each time a Grafana update brings<br/>changes to said schema.<br/>TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion |
|
||||
| `style` | string | **Yes** | `dark` | Theme of dashboard.<br/>Possible values are: `dark`, `light`. |
|
||||
| `annotations` | [AnnotationContainer](#annotationcontainer) | No | | TODO -- should not be a public interface on its own, but required for Veneer |
|
||||
| `description` | string | No | | Description of dashboard. |
|
||||
| `fiscalYearStartMonth` | integer | No | `0` | The month that the fiscal year starts on. 0 = January, 11 = December<br/>Constraint: `>=0 & <12`. |
|
||||
| `gnetId` | string | No | | For dashboards imported from the https://grafana.com/grafana/dashboards/ portal |
|
||||
| `id` | integer | No | | Unique numeric identifier for the dashboard.<br/>TODO must isolate or remove identifiers local to a Grafana instance...? |
|
||||
| `links` | [DashboardLink](#dashboardlink)[] | No | | TODO docs |
|
||||
| `liveNow` | boolean | No | | When set to true, the dashboard will redraw panels at an interval matching the pixel width.<br/>This will keep data "moving left" regardless of the query refresh rate. This setting helps<br/>avoid dashboards presenting stale live data |
|
||||
| `panels` | [object](#panels)[] | No | | |
|
||||
| `refresh` | | No | | Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". |
|
||||
| `revision` | integer | No | | This property should only be used in dashboards defined by plugins. It is a quick check<br/>to see if the version has changed since the last time. Unclear why using the version property<br/>is insufficient. |
|
||||
| `snapshot` | [Snapshot](#snapshot) | No | | TODO docs |
|
||||
| `tags` | string[] | No | | Tags associated with dashboard. |
|
||||
| `templating` | [object](#templating) | No | | TODO docs |
|
||||
| `time` | [object](#time) | No | | Time range for dashboard, e.g. last 6 hours, last 7 days, etc |
|
||||
| `timepicker` | [object](#timepicker) | No | | TODO docs<br/>TODO this appears to be spread all over in the frontend. Concepts will likely need tidying in tandem with schema changes |
|
||||
| `timezone` | string | No | `browser` | Timezone of dashboard. Accepts IANA TZDB zone ID or "browser" or "utc". |
|
||||
| `title` | string | No | | Title of dashboard. |
|
||||
| `uid` | string | No | | Unique dashboard identifier that can be generated by anyone. string (8-40) |
|
||||
| `version` | uint32 | No | | Version of the dashboard, incremented each time the dashboard is updated. |
|
||||
| `weekStart` | string | No | | TODO docs |
|
||||
|
||||
### AnnotationContainer
|
||||
|
||||
TODO -- should not be a public interface on its own, but required for Veneer
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------|---------------------------------------|----------|---------|-------------|
|
||||
| `list` | [AnnotationQuery](#annotationquery)[] | No | | |
|
||||
|
||||
### AnnotationQuery
|
||||
|
||||
TODO docs
|
||||
FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|--------------|-------------------------------------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `datasource` | [object](#datasource) | **Yes** | | TODO: Should be DataSourceRef |
|
||||
| `enable` | boolean | **Yes** | `true` | When enabled the annotation query is issued with every dashboard refresh |
|
||||
| `iconColor` | string | **Yes** | | Color to use for the annotation event markers |
|
||||
| `name` | string | **Yes** | | Name of annotation. |
|
||||
| `filter` | [AnnotationPanelFilter](#annotationpanelfilter) | No | | |
|
||||
| `hide` | boolean | No | `false` | Annotation queries can be toggled on or off at the top of the dashboard.<br/>When hide is true, the toggle is not shown in the dashboard. |
|
||||
| `target` | [AnnotationTarget](#annotationtarget) | No | | TODO: this should be a regular DataQuery that depends on the selected dashboard<br/>these match the properties of the "grafana" datasouce that is default in most dashboards |
|
||||
| `type` | string | No | | TODO -- this should not exist here, it is based on the --grafana-- datasource |
|
||||
|
||||
### AnnotationPanelFilter
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|-----------|-----------|----------|---------|-----------------------------------------------------|
|
||||
| `ids` | integer[] | **Yes** | | Panel IDs that should be included or excluded |
|
||||
| `exclude` | boolean | No | `false` | Should the specified panels be included or excluded |
|
||||
|
||||
### AnnotationTarget
|
||||
|
||||
TODO: this should be a regular DataQuery that depends on the selected dashboard
|
||||
these match the properties of the "grafana" datasouce that is default in most dashboards
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|------------|----------|----------|---------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| `limit` | integer | **Yes** | | Only required/valid for the grafana datasource...<br/>but code+tests is already depending on it so hard to change |
|
||||
| `matchAny` | boolean | **Yes** | | Only required/valid for the grafana datasource...<br/>but code+tests is already depending on it so hard to change |
|
||||
| `tags` | string[] | **Yes** | | Only required/valid for the grafana datasource...<br/>but code+tests is already depending on it so hard to change |
|
||||
| `type` | string | **Yes** | | Only required/valid for the grafana datasource...<br/>but code+tests is already depending on it so hard to change |
|
||||
|
||||
### Datasource
|
||||
|
||||
TODO: Should be DataSourceRef
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------|--------|----------|---------|-------------|
|
||||
| `type` | string | No | | |
|
||||
| `uid` | string | No | | |
|
||||
|
||||
### DashboardLink
|
||||
|
||||
@ -76,52 +128,6 @@ TODO docs
|
||||
| `userId` | uint32 | **Yes** | | TODO docs |
|
||||
| `url` | string | No | | TODO docs |
|
||||
|
||||
### Annotations
|
||||
|
||||
TODO docs
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------|---------------------------------------|----------|---------|-------------|
|
||||
| `list` | [AnnotationQuery](#annotationquery)[] | No | | |
|
||||
|
||||
### AnnotationQuery
|
||||
|
||||
TODO docs
|
||||
FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|--------------|---------------------------------------|----------|-------------|-----------------------------------|
|
||||
| `builtIn` | uint8 | **Yes** | `0` | |
|
||||
| `datasource` | [object](#datasource) | **Yes** | | Datasource to use for annotation. |
|
||||
| `enable` | boolean | **Yes** | `true` | Whether annotation is enabled. |
|
||||
| `showIn` | uint8 | **Yes** | `0` | |
|
||||
| `type` | string | **Yes** | `dashboard` | |
|
||||
| `hide` | boolean | No | `false` | Whether to hide annotation. |
|
||||
| `iconColor` | string | No | | Annotation icon color. |
|
||||
| `name` | string | No | | Name of annotation. |
|
||||
| `rawQuery` | string | No | | Query for annotation data. |
|
||||
| `target` | [AnnotationTarget](#annotationtarget) | No | | TODO docs |
|
||||
|
||||
### AnnotationTarget
|
||||
|
||||
TODO docs
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|------------|----------|----------|---------|-------------|
|
||||
| `limit` | integer | **Yes** | | |
|
||||
| `matchAny` | boolean | **Yes** | | |
|
||||
| `tags` | string[] | **Yes** | | |
|
||||
| `type` | string | **Yes** | | |
|
||||
|
||||
### Datasource
|
||||
|
||||
Datasource to use for annotation.
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------|--------|----------|---------|-------------|
|
||||
| `type` | string | No | | |
|
||||
| `uid` | string | No | | |
|
||||
|
||||
### Panels
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|
61
e2e/various-suite/filter-annotations.spec.ts
Normal file
61
e2e/various-suite/filter-annotations.spec.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
const DASHBOARD_ID = 'ed155665';
|
||||
|
||||
e2e.scenario({
|
||||
describeName: 'Annotations filtering',
|
||||
itName: 'Tests switching filter type updates the UI accordingly',
|
||||
addScenarioDataSource: false,
|
||||
addScenarioDashBoard: false,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
|
||||
e2e.components.PageToolbar.item('Dashboard settings').click();
|
||||
e2e.components.Tab.title('Annotations').click();
|
||||
cy.contains('New query').click();
|
||||
e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type('Red - Panel two');
|
||||
|
||||
e2e.pages.Dashboard.Settings.Annotations.NewAnnotation.showInLabel()
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
// All panels
|
||||
e2e.components.Annotations.annotationsTypeInput().click({ force: true }).type('All panels{enter}');
|
||||
e2e.components.Annotations.annotationsChoosePanelInput().should('not.exist');
|
||||
|
||||
// All panels except
|
||||
e2e.components.Annotations.annotationsTypeInput().click({ force: true }).type('All panels except{enter}');
|
||||
e2e.components.Annotations.annotationsChoosePanelInput().should('be.visible');
|
||||
|
||||
// Selected panels
|
||||
e2e.components.Annotations.annotationsTypeInput().click({ force: true }).type('Selected panels{enter}');
|
||||
e2e.components.Annotations.annotationsChoosePanelInput()
|
||||
.should('be.visible')
|
||||
.click({ force: true })
|
||||
.type('Panel two{enter}');
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Settings.Annotations.NewAnnotation.previewInDashboard().click({ force: true });
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.Annotations.annotationsWrapper()
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
e2e.pages.Dashboard.SubMenu.Annotations.annotationLabel('Red - Panel two').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.Annotations.annotationToggle('Red - Panel two')
|
||||
.should('be.checked')
|
||||
.uncheck({ force: true })
|
||||
.should('not.be.checked')
|
||||
.check({ force: true });
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.Annotations.annotationLabel('Red, only panel 1').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.Annotations.annotationToggle('Red, only panel 1').should('be.checked');
|
||||
});
|
||||
|
||||
e2e().wait(3000);
|
||||
|
||||
e2e.components.Panels.Panel.title('Panel one')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
e2e.pages.SoloPanel.Annotations.marker().should('exist').should('have.length', 4);
|
||||
});
|
||||
},
|
||||
});
|
@ -85,10 +85,18 @@ lineage: seqs: [
|
||||
templating?: {
|
||||
list?: [...#VariableModel] @grafanamaturity(NeedsExpertReview)
|
||||
}
|
||||
// TODO docs
|
||||
annotations?: {
|
||||
|
||||
// TODO -- should not be a public interface on its own, but required for Veneer
|
||||
#AnnotationContainer: {
|
||||
// annoying... but required so that the list is defined using the nested Veneer
|
||||
@grafana(TSVeneer="type")
|
||||
|
||||
list?: [...#AnnotationQuery] @grafanamaturity(NeedsExpertReview)
|
||||
}
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// TODO docs
|
||||
annotations?: #AnnotationContainer
|
||||
|
||||
// TODO docs
|
||||
links?: [...#DashboardLink] @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
@ -97,39 +105,72 @@ lineage: seqs: [
|
||||
///////////////////////////////////////
|
||||
// Definitions (referenced above) are declared below
|
||||
|
||||
// TODO docs
|
||||
// TODO: this should be a regular DataQuery that depends on the selected dashboard
|
||||
// these match the properties of the "grafana" datasouce that is default in most dashboards
|
||||
#AnnotationTarget: {
|
||||
limit: int64
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
limit: int64
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
matchAny: bool
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
tags: [...string]
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
type: string
|
||||
... // datasource will stick their raw DataQuery here
|
||||
} @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
#AnnotationPanelFilter: {
|
||||
// Should the specified panels be included or excluded
|
||||
exclude?: bool | *false
|
||||
|
||||
// Panel IDs that should be included or excluded
|
||||
ids: [...uint8]
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// TODO docs
|
||||
// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
|
||||
#AnnotationQuery: {
|
||||
// Datasource to use for annotation.
|
||||
@grafana(TSVeneer="type")
|
||||
|
||||
// Name of annotation.
|
||||
name: string
|
||||
|
||||
// TODO: Should be DataSourceRef
|
||||
datasource: {
|
||||
type?: string
|
||||
uid?: string
|
||||
} @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
// Whether annotation is enabled.
|
||||
enable: bool | *true @grafanamaturity(NeedsExpertReview)
|
||||
// Name of annotation.
|
||||
name?: string @grafanamaturity(NeedsExpertReview)
|
||||
builtIn: uint8 | *0 @grafanamaturity(NeedsExpertReview) // TODO should this be persisted at all?
|
||||
// Whether to hide annotation.
|
||||
hide?: bool | *false @grafanamaturity(NeedsExpertReview)
|
||||
// Annotation icon color.
|
||||
iconColor?: string @grafanamaturity(NeedsExpertReview)
|
||||
type: string | *"dashboard" @grafanamaturity(NeedsExpertReview)
|
||||
// Query for annotation data.
|
||||
rawQuery?: string @grafanamaturity(NeedsExpertReview)
|
||||
showIn: uint8 | *0 @grafanamaturity(NeedsExpertReview)
|
||||
target?: #AnnotationTarget @grafanamaturity(NeedsExpertReview)
|
||||
// When enabled the annotation query is issued with every dashboard refresh
|
||||
enable: bool | *true
|
||||
|
||||
// Annotation queries can be toggled on or off at the top of the dashboard.
|
||||
// When hide is true, the toggle is not shown in the dashboard.
|
||||
hide?: bool | *false
|
||||
|
||||
// Color to use for the annotation event markers
|
||||
iconColor: string
|
||||
|
||||
// Optionally
|
||||
filter?: #AnnotationPanelFilter
|
||||
|
||||
// TODO.. this should just be a normal query target
|
||||
target?: #AnnotationTarget
|
||||
|
||||
// TODO -- this should not exist here, it is based on the --grafana-- datasource
|
||||
type?: string @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
// unless datasources have migrated to the target+mapping,
|
||||
// they just spread their query into the base object :(
|
||||
...
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
#LoadingState: "NotStarted" | "Loading" | "Streaming" | "Done" | "Error" @cuetsy(kind="enum") @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
// FROM: packages/grafana-data/src/types/templateVars.ts
|
||||
// TODO docs
|
||||
// TODO what about what's in public/app/features/types.ts?
|
||||
|
@ -1,30 +1,23 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DataQuery, AnnotationQuery as SchemaAnnotationQuery } from '@grafana/schema';
|
||||
|
||||
import { DataFrame } from './dataFrame';
|
||||
import { QueryEditorProps } from './datasource';
|
||||
import { DataQuery, DataSourceRef } from './query';
|
||||
|
||||
/**
|
||||
* This JSON object is stored in the dashboard json model.
|
||||
*/
|
||||
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
|
||||
datasource?: DataSourceRef | null;
|
||||
|
||||
enable: boolean;
|
||||
name: string;
|
||||
iconColor: string;
|
||||
hide?: boolean;
|
||||
builtIn?: number;
|
||||
type?: string;
|
||||
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> extends SchemaAnnotationQuery<TQuery> {
|
||||
snapshotData?: any;
|
||||
|
||||
// Standard datasource query
|
||||
target?: TQuery;
|
||||
|
||||
// Convert a dataframe to an AnnotationEvent
|
||||
mappings?: AnnotationEventMappings;
|
||||
|
||||
// When using the 'grafana' datasource, this may be dashboard
|
||||
type?: string;
|
||||
|
||||
// Sadly plugins can set any property directly on the main object
|
||||
[key: string]: any;
|
||||
}
|
||||
@ -51,7 +44,7 @@ export interface AnnotationEvent {
|
||||
newState?: string;
|
||||
|
||||
// Currently used to merge annotations from alerts and dashboard
|
||||
source?: any; // source.type === 'dashboard'
|
||||
source?: any; // source.type === 'dashboard' -- should be AnnotationQuery
|
||||
}
|
||||
|
||||
export interface AnnotationEventUIModel {
|
||||
|
@ -57,6 +57,7 @@ export class DataSourcePlugin<
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated -- register the annotation support in the instance constructor */
|
||||
setAnnotationQueryCtrl(AnnotationsQueryCtrl: any) {
|
||||
this.components.AnnotationsQueryCtrl = AnnotationsQueryCtrl;
|
||||
return this;
|
||||
|
@ -416,4 +416,8 @@ export const Components = {
|
||||
Variables: {
|
||||
variableOption: 'data-testid variable-option',
|
||||
},
|
||||
Annotations: {
|
||||
annotationsTypeInput: 'annotations-type-input',
|
||||
annotationsChoosePanelInput: 'choose-panels-input',
|
||||
},
|
||||
};
|
||||
|
@ -66,6 +66,11 @@ export const Pages = {
|
||||
submenuItemValueDropDownDropDown: 'Variable options',
|
||||
submenuItemValueDropDownOptionTexts: (item: string) =>
|
||||
`data-testid Dashboard template variables Variable Value DropDown option text ${item}`,
|
||||
Annotations: {
|
||||
annotationsWrapper: 'data-testid annotation-wrapper',
|
||||
annotationLabel: (label: string) => `data-testid Dashboard annotations submenu Label ${label}`,
|
||||
annotationToggle: (label: string) => `data-testid Dashboard annotations submenu Toggle ${label}`,
|
||||
},
|
||||
},
|
||||
Settings: {
|
||||
Actions: {
|
||||
@ -93,6 +98,11 @@ export const Pages = {
|
||||
Settings: {
|
||||
name: 'Annotations settings name input',
|
||||
},
|
||||
NewAnnotation: {
|
||||
panelFilterSelect: 'data-testid annotations-panel-filter',
|
||||
showInLabel: 'show-in-label',
|
||||
previewInDashboard: 'data-testid annotations-preview',
|
||||
},
|
||||
},
|
||||
Variables: {
|
||||
List: {
|
||||
@ -239,6 +249,9 @@ export const Pages = {
|
||||
},
|
||||
SoloPanel: {
|
||||
url: (page: string) => `/d-solo/${page}`,
|
||||
Annotations: {
|
||||
marker: 'data-testid annotation-marker',
|
||||
},
|
||||
},
|
||||
PluginsList: {
|
||||
page: 'Plugins list page',
|
||||
|
@ -10,7 +10,7 @@
|
||||
// Raw generated types from Dashboard kind.
|
||||
export type {
|
||||
AnnotationTarget,
|
||||
AnnotationQuery,
|
||||
AnnotationPanelFilter,
|
||||
DashboardLink,
|
||||
DashboardLinkType,
|
||||
VariableType,
|
||||
@ -34,7 +34,7 @@ export type {
|
||||
// Raw generated enums and default consts from dashboard kind.
|
||||
export {
|
||||
defaultAnnotationTarget,
|
||||
defaultAnnotationQuery,
|
||||
defaultAnnotationPanelFilter,
|
||||
LoadingState,
|
||||
defaultDashboardLink,
|
||||
FieldColorModeId,
|
||||
@ -59,6 +59,8 @@ export {
|
||||
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls
|
||||
export type {
|
||||
Dashboard,
|
||||
AnnotationContainer,
|
||||
AnnotationQuery,
|
||||
VariableModel,
|
||||
DataSourceRef,
|
||||
DataTransformerConfig,
|
||||
@ -79,6 +81,8 @@ export type {
|
||||
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls
|
||||
export {
|
||||
defaultDashboard,
|
||||
defaultAnnotationContainer,
|
||||
defaultAnnotationQuery,
|
||||
defaultVariableModel,
|
||||
VariableHide,
|
||||
defaultPanel,
|
||||
|
@ -9,12 +9,40 @@
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
/**
|
||||
* TODO docs
|
||||
* TODO -- should not be a public interface on its own, but required for Veneer
|
||||
*/
|
||||
export interface AnnotationContainer {
|
||||
list?: Array<AnnotationQuery>;
|
||||
}
|
||||
|
||||
export const defaultAnnotationContainer: Partial<AnnotationContainer> = {
|
||||
list: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: this should be a regular DataQuery that depends on the selected dashboard
|
||||
* these match the properties of the "grafana" datasouce that is default in most dashboards
|
||||
*/
|
||||
export interface AnnotationTarget {
|
||||
/**
|
||||
* Only required/valid for the grafana datasource...
|
||||
* but code+tests is already depending on it so hard to change
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* Only required/valid for the grafana datasource...
|
||||
* but code+tests is already depending on it so hard to change
|
||||
*/
|
||||
matchAny: boolean;
|
||||
/**
|
||||
* Only required/valid for the grafana datasource...
|
||||
* but code+tests is already depending on it so hard to change
|
||||
*/
|
||||
tags: Array<string>;
|
||||
/**
|
||||
* Only required/valid for the grafana datasource...
|
||||
* but code+tests is already depending on it so hard to change
|
||||
*/
|
||||
type: string;
|
||||
}
|
||||
|
||||
@ -22,52 +50,78 @@ export const defaultAnnotationTarget: Partial<AnnotationTarget> = {
|
||||
tags: [],
|
||||
};
|
||||
|
||||
export interface AnnotationPanelFilter {
|
||||
/**
|
||||
* Should the specified panels be included or excluded
|
||||
*/
|
||||
exclude?: boolean;
|
||||
/**
|
||||
* Panel IDs that should be included or excluded
|
||||
*/
|
||||
ids: Array<number>;
|
||||
}
|
||||
|
||||
export const defaultAnnotationPanelFilter: Partial<AnnotationPanelFilter> = {
|
||||
exclude: false,
|
||||
ids: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO docs
|
||||
* FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
|
||||
*/
|
||||
export interface AnnotationQuery {
|
||||
builtIn: number; // TODO should this be persisted at all?
|
||||
/**
|
||||
* Datasource to use for annotation.
|
||||
* TODO: Should be DataSourceRef
|
||||
*/
|
||||
datasource: {
|
||||
type?: string;
|
||||
uid?: string;
|
||||
};
|
||||
/**
|
||||
* Whether annotation is enabled.
|
||||
* When enabled the annotation query is issued with every dashboard refresh
|
||||
*/
|
||||
enable: boolean;
|
||||
/**
|
||||
* Whether to hide annotation.
|
||||
* Optionally
|
||||
*/
|
||||
filter?: AnnotationPanelFilter;
|
||||
/**
|
||||
* Annotation queries can be toggled on or off at the top of the dashboard.
|
||||
* When hide is true, the toggle is not shown in the dashboard.
|
||||
*/
|
||||
hide?: boolean;
|
||||
/**
|
||||
* Annotation icon color.
|
||||
* Color to use for the annotation event markers
|
||||
*/
|
||||
iconColor?: string;
|
||||
iconColor: string;
|
||||
/**
|
||||
* Name of annotation.
|
||||
*/
|
||||
name?: string;
|
||||
name: string;
|
||||
/**
|
||||
* Query for annotation data.
|
||||
* TODO.. this should just be a normal query target
|
||||
*/
|
||||
rawQuery?: string;
|
||||
showIn: number;
|
||||
target?: AnnotationTarget;
|
||||
type: string;
|
||||
/**
|
||||
* TODO -- this should not exist here, it is based on the --grafana-- datasource
|
||||
*/
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export const defaultAnnotationQuery: Partial<AnnotationQuery> = {
|
||||
builtIn: 0,
|
||||
enable: true,
|
||||
hide: false,
|
||||
showIn: 0,
|
||||
type: 'dashboard',
|
||||
};
|
||||
|
||||
export enum LoadingState {
|
||||
Done = 'Done',
|
||||
Error = 'Error',
|
||||
Loading = 'Loading',
|
||||
NotStarted = 'NotStarted',
|
||||
Streaming = 'Streaming',
|
||||
}
|
||||
|
||||
/**
|
||||
* FROM: packages/grafana-data/src/types/templateVars.ts
|
||||
* TODO docs
|
||||
@ -107,14 +161,6 @@ export enum VariableHide {
|
||||
hideVariable = 2,
|
||||
}
|
||||
|
||||
export enum LoadingState {
|
||||
Done = 'Done',
|
||||
Error = 'Error',
|
||||
Loading = 'Loading',
|
||||
NotStarted = 'NotStarted',
|
||||
Streaming = 'Streaming',
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref to a DataSource instance
|
||||
*/
|
||||
@ -662,9 +708,7 @@ export interface Dashboard {
|
||||
/**
|
||||
* TODO docs
|
||||
*/
|
||||
annotations?: {
|
||||
list?: Array<AnnotationQuery>;
|
||||
};
|
||||
annotations?: AnnotationContainer;
|
||||
/**
|
||||
* Description of dashboard.
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { DataSourceRef as CommonDataSourceRef } from '../common/common.gen';
|
||||
import { DataSourceRef as CommonDataSourceRef, DataSourceRef } from '../common/common.gen';
|
||||
import * as raw from '../raw/dashboard/x/dashboard_types.gen';
|
||||
|
||||
import { DataQuery } from './common.types';
|
||||
|
||||
export type { CommonDataSourceRef as DataSourceRef };
|
||||
|
||||
export interface Panel<TOptions = Record<string, unknown>, TCustomFieldConfig = Record<string, unknown>>
|
||||
@ -28,13 +30,24 @@ export interface VariableModel
|
||||
datasource: CommonDataSourceRef | null;
|
||||
}
|
||||
|
||||
export interface Dashboard extends Omit<raw.Dashboard, 'templating'> {
|
||||
export interface Dashboard extends Omit<raw.Dashboard, 'templating' | 'annotations'> {
|
||||
panels?: Array<Panel | raw.RowPanel | raw.GraphPanel | raw.HeatmapPanel>;
|
||||
annotations?: AnnotationContainer;
|
||||
templating?: {
|
||||
list?: VariableModel[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery>
|
||||
extends Omit<raw.AnnotationQuery, 'target' | 'datasource'> {
|
||||
datasource?: DataSourceRef | null;
|
||||
target?: TQuery;
|
||||
}
|
||||
|
||||
export interface AnnotationContainer extends Omit<raw.AnnotationContainer, 'list'> {
|
||||
list?: AnnotationQuery[]; // use the version from this file
|
||||
}
|
||||
|
||||
export interface FieldConfig<TOptions = Record<string, unknown>> extends raw.FieldConfig {
|
||||
custom?: TOptions & Record<string, unknown>;
|
||||
}
|
||||
@ -69,3 +82,6 @@ export const defaultPanel: Partial<Panel> = raw.defaultPanel;
|
||||
export const defaultFieldConfig: Partial<FieldConfig> = raw.defaultFieldConfig;
|
||||
export const defaultFieldConfigSource: Partial<FieldConfigSource> = raw.defaultFieldConfigSource;
|
||||
export const defaultMatcherConfig: Partial<MatcherConfig> = raw.defaultMatcherConfig;
|
||||
export const defaultAnnotationQuery: Partial<AnnotationQuery> = raw.defaultAnnotationQuery as AnnotationQuery;
|
||||
export const defaultAnnotationContainer: Partial<AnnotationContainer> =
|
||||
raw.defaultAnnotationContainer as AnnotationContainer;
|
||||
|
@ -160,52 +160,75 @@ const (
|
||||
VariableTypeTextbox VariableType = "textbox"
|
||||
)
|
||||
|
||||
// TODO -- should not be a public interface on its own, but required for Veneer
|
||||
type AnnotationContainer struct {
|
||||
List []AnnotationQuery `json:"list,omitempty"`
|
||||
}
|
||||
|
||||
// AnnotationPanelFilter defines model for AnnotationPanelFilter.
|
||||
type AnnotationPanelFilter struct {
|
||||
// Should the specified panels be included or excluded
|
||||
Exclude *bool `json:"exclude,omitempty"`
|
||||
|
||||
// Panel IDs that should be included or excluded
|
||||
Ids []int `json:"ids"`
|
||||
}
|
||||
|
||||
// TODO docs
|
||||
// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
|
||||
type AnnotationQuery struct {
|
||||
BuiltIn int `json:"builtIn"`
|
||||
|
||||
// Datasource to use for annotation.
|
||||
// TODO: Should be DataSourceRef
|
||||
Datasource struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
} `json:"datasource"`
|
||||
|
||||
// Whether annotation is enabled.
|
||||
Enable bool `json:"enable"`
|
||||
// When enabled the annotation query is issued with every dashboard refresh
|
||||
Enable bool `json:"enable"`
|
||||
Filter *AnnotationPanelFilter `json:"filter,omitempty"`
|
||||
|
||||
// Whether to hide annotation.
|
||||
// Annotation queries can be toggled on or off at the top of the dashboard.
|
||||
// When hide is true, the toggle is not shown in the dashboard.
|
||||
Hide *bool `json:"hide,omitempty"`
|
||||
|
||||
// Annotation icon color.
|
||||
IconColor *string `json:"iconColor,omitempty"`
|
||||
// Color to use for the annotation event markers
|
||||
IconColor string `json:"iconColor"`
|
||||
|
||||
// Name of annotation.
|
||||
Name *string `json:"name,omitempty"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Query for annotation data.
|
||||
RawQuery *string `json:"rawQuery,omitempty"`
|
||||
ShowIn int `json:"showIn"`
|
||||
|
||||
// TODO docs
|
||||
// TODO: this should be a regular DataQuery that depends on the selected dashboard
|
||||
// these match the properties of the "grafana" datasouce that is default in most dashboards
|
||||
Target *AnnotationTarget `json:"target,omitempty"`
|
||||
Type string `json:"type"`
|
||||
|
||||
// TODO -- this should not exist here, it is based on the --grafana-- datasource
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// TODO docs
|
||||
// TODO: this should be a regular DataQuery that depends on the selected dashboard
|
||||
// these match the properties of the "grafana" datasouce that is default in most dashboards
|
||||
type AnnotationTarget struct {
|
||||
Limit int64 `json:"limit"`
|
||||
MatchAny bool `json:"matchAny"`
|
||||
Tags []string `json:"tags"`
|
||||
Type string `json:"type"`
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
Limit int64 `json:"limit"`
|
||||
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
MatchAny bool `json:"matchAny"`
|
||||
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
Tags []string `json:"tags"`
|
||||
|
||||
// Only required/valid for the grafana datasource...
|
||||
// but code+tests is already depending on it so hard to change
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Dashboard defines model for Dashboard.
|
||||
type Dashboard struct {
|
||||
// TODO docs
|
||||
Annotations *struct {
|
||||
List []AnnotationQuery `json:"list,omitempty"`
|
||||
} `json:"annotations,omitempty"`
|
||||
// TODO -- should not be a public interface on its own, but required for Veneer
|
||||
Annotations *AnnotationContainer `json:"annotations,omitempty"`
|
||||
|
||||
// Description of dashboard.
|
||||
Description *string `json:"description,omitempty"`
|
||||
|
@ -70,7 +70,7 @@ func (pd *PublicDashboardServiceImpl) FindAnnotations(ctx context.Context, reqDT
|
||||
Tags: item.Tags,
|
||||
IsRegion: item.TimeEnd > 0 && item.Time != item.TimeEnd,
|
||||
Text: item.Text,
|
||||
Color: *anno.IconColor,
|
||||
Color: anno.IconColor,
|
||||
Time: item.Time,
|
||||
TimeEnd: item.TimeEnd,
|
||||
Source: anno,
|
||||
@ -78,7 +78,7 @@ func (pd *PublicDashboardServiceImpl) FindAnnotations(ctx context.Context, reqDT
|
||||
|
||||
// We want dashboard annotations to reference the panel they're for. If no panelId is provided, they'll show up on all panels
|
||||
// which is only intended for tag and org annotations.
|
||||
if anno.Type == "dashboard" {
|
||||
if anno.Type != nil && *anno.Type == "dashboard" {
|
||||
event.PanelId = item.PanelID
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -743,21 +744,21 @@ func TestFindAnnotations(t *testing.T) {
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
Tags: nil,
|
||||
Type: "dashboard",
|
||||
},
|
||||
Type: "dashboard",
|
||||
Type: util.Pointer("dashboard"),
|
||||
}
|
||||
grafanaTagAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
@ -816,8 +817,8 @@ func TestFindAnnotations(t *testing.T) {
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
@ -876,26 +877,26 @@ func TestFindAnnotations(t *testing.T) {
|
||||
disabledGrafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: false,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
}
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: true,
|
||||
Tags: nil,
|
||||
Type: "dashboard",
|
||||
},
|
||||
Type: "dashboard",
|
||||
Type: util.Pointer("dashboard"),
|
||||
}
|
||||
queryAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("prometheus", "abc123"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
Name: name,
|
||||
}
|
||||
annos := []DashAnnotation{grafanaAnnotation, queryAnnotation, disabledGrafanaAnnotation}
|
||||
dashboard := AddAnnotationsToDashboard(t, dash, annos)
|
||||
@ -975,15 +976,15 @@ func TestFindAnnotations(t *testing.T) {
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
Tags: nil,
|
||||
Type: "dashboard",
|
||||
},
|
||||
Type: "dashboard",
|
||||
Type: util.Pointer("dashboard"),
|
||||
}
|
||||
annos := []DashAnnotation{grafanaAnnotation}
|
||||
dashboard := AddAnnotationsToDashboard(t, dash, annos)
|
||||
@ -1010,8 +1011,8 @@ func TestFindAnnotations(t *testing.T) {
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
@ -1039,9 +1040,9 @@ func TestFindAnnotations(t *testing.T) {
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Type: "dashboard",
|
||||
Name: name,
|
||||
IconColor: color,
|
||||
Type: util.Pointer("dashboard"),
|
||||
Target: nil,
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,31 @@
|
||||
import React, { useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { AnnotationQuery, DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data';
|
||||
import {
|
||||
AnnotationQuery,
|
||||
DataSourceInstanceSettings,
|
||||
getDataSourceRef,
|
||||
GrafanaTheme2,
|
||||
SelectableValue,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { DataSourcePicker, getDataSourceSrv, locationService } from '@grafana/runtime';
|
||||
import { Button, Checkbox, Field, FieldSet, HorizontalGroup, Input } from '@grafana/ui';
|
||||
import { AnnotationPanelFilter } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Field,
|
||||
FieldSet,
|
||||
HorizontalGroup,
|
||||
Input,
|
||||
MultiSelect,
|
||||
Select,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import { ColorValueEditor } from 'app/core/components/OptionsUI/color';
|
||||
import config from 'app/core/config';
|
||||
import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor';
|
||||
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
@ -21,8 +40,16 @@ type Props = {
|
||||
export const newAnnotationName = 'New annotation';
|
||||
|
||||
export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [annotation, setAnnotation] = useState(dashboard.annotations.list[editIdx]);
|
||||
|
||||
const panelFilter = useMemo(() => {
|
||||
if (!annotation.filter) {
|
||||
return PanelFilterType.AllPanels;
|
||||
}
|
||||
return annotation.filter.exclude ? PanelFilterType.ExcludePanels : PanelFilterType.IncludePanels;
|
||||
}, [annotation.filter]);
|
||||
|
||||
const { value: ds } = useAsync(() => {
|
||||
return getDataSourceSrv().get(annotation.datasource);
|
||||
}, [annotation.datasource]);
|
||||
@ -65,6 +92,31 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onFilterTypeChange = (v: SelectableValue<PanelFilterType>) => {
|
||||
let filter =
|
||||
v.value === PanelFilterType.AllPanels
|
||||
? undefined
|
||||
: {
|
||||
exclude: v.value === PanelFilterType.ExcludePanels,
|
||||
ids: annotation.filter?.ids ?? [],
|
||||
};
|
||||
onUpdate({ ...annotation, filter });
|
||||
};
|
||||
|
||||
const onAddFilterPanelID = (selections: Array<SelectableValue<number>>) => {
|
||||
if (!Array.isArray(selections)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filter: AnnotationPanelFilter = {
|
||||
exclude: panelFilter === PanelFilterType.ExcludePanels,
|
||||
ids: [],
|
||||
};
|
||||
|
||||
selections.forEach((selection) => selection.value && filter.ids.push(selection.value));
|
||||
onUpdate({ ...annotation, filter });
|
||||
};
|
||||
|
||||
const onApply = goBackToList;
|
||||
|
||||
const onPreview = () => {
|
||||
@ -79,9 +131,30 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
|
||||
const isNewAnnotation = annotation.name === newAnnotationName;
|
||||
|
||||
const sortFn = (a: SelectableValue<number>, b: SelectableValue<number>) => {
|
||||
if (a.label && b.label) {
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
||||
const panels: Array<SelectableValue<number>> = useMemo(
|
||||
() =>
|
||||
dashboard?.panels
|
||||
.map((panel) => ({
|
||||
value: panel.id,
|
||||
label: panel.title ?? `Panel ${panel.id}`,
|
||||
description: panel.description,
|
||||
imgUrl: config.panels[panel.type].info.logos.small,
|
||||
}))
|
||||
.sort(sortFn) ?? [],
|
||||
[dashboard]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldSet>
|
||||
<FieldSet className={styles.settingsForm}>
|
||||
<Field label="Name">
|
||||
<Input
|
||||
aria-label={selectors.pages.Dashboard.Settings.Annotations.Settings.name}
|
||||
@ -90,17 +163,10 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
autoFocus={isNewAnnotation}
|
||||
value={annotation.name}
|
||||
onChange={onNameChange}
|
||||
width={50}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Data source" htmlFor="data-source-picker">
|
||||
<DataSourcePicker
|
||||
width={50}
|
||||
annotations
|
||||
variables
|
||||
current={annotation.datasource}
|
||||
onChange={onDataSourceChange}
|
||||
/>
|
||||
<DataSourcePicker annotations variables current={annotation.datasource} onChange={onDataSourceChange} />
|
||||
</Field>
|
||||
<Field label="Enabled" description="When enabled the annotation query is issued every dashboard refresh">
|
||||
<Checkbox name="enable" id="enable" value={annotation.enable} onChange={onChange} />
|
||||
@ -116,6 +182,31 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
<ColorValueEditor value={annotation?.iconColor} onChange={onColorChange} />
|
||||
</HorizontalGroup>
|
||||
</Field>
|
||||
<Field label="Show in" aria-label={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.showInLabel}>
|
||||
<>
|
||||
<Select
|
||||
options={panelFilters}
|
||||
value={panelFilter}
|
||||
onChange={onFilterTypeChange}
|
||||
aria-label={selectors.components.Annotations.annotationsTypeInput}
|
||||
/>
|
||||
{panelFilter !== PanelFilterType.AllPanels && (
|
||||
<MultiSelect
|
||||
options={panels}
|
||||
value={panels.filter((panel) => annotation.filter?.ids.includes(panel.value!))}
|
||||
onChange={onAddFilterPanelID}
|
||||
isClearable={true}
|
||||
placeholder="Choose panels"
|
||||
width={100}
|
||||
closeMenuOnSelect={false}
|
||||
className={styles.select}
|
||||
aria-label={selectors.components.Annotations.annotationsChoosePanelInput}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Field>
|
||||
</FieldSet>
|
||||
<FieldSet>
|
||||
<h3 className="page-heading">Query</h3>
|
||||
{ds?.annotations && dsi && (
|
||||
<StandardAnnotationQueryEditor
|
||||
@ -133,7 +224,11 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="secondary" onClick={onPreview}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onPreview}
|
||||
data-testid={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.previewInDashboard}
|
||||
>
|
||||
Preview in dashboard
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onApply}>
|
||||
@ -144,8 +239,43 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
AnnotationSettingsEdit.displayName = 'AnnotationSettingsEdit';
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
settingsForm: css({
|
||||
maxWidth: theme.spacing(60),
|
||||
marginBottom: theme.spacing(2),
|
||||
}),
|
||||
select: css`
|
||||
margin-top: 8px;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
function goBackToList() {
|
||||
locationService.partial({ editIndex: null });
|
||||
}
|
||||
|
||||
// Synthetic type
|
||||
enum PanelFilterType {
|
||||
AllPanels,
|
||||
IncludePanels,
|
||||
ExcludePanels,
|
||||
}
|
||||
|
||||
const panelFilters = [
|
||||
{
|
||||
label: 'All panels',
|
||||
value: PanelFilterType.AllPanels,
|
||||
description: 'Send the annotation data to all panels that support annotations',
|
||||
},
|
||||
{
|
||||
label: 'Selected panels',
|
||||
value: PanelFilterType.IncludePanels,
|
||||
description: 'Send the annotations to the explicitly listed panels',
|
||||
},
|
||||
{
|
||||
label: 'All panels except',
|
||||
value: PanelFilterType.ExcludePanels,
|
||||
description: 'Do not send annotation data to the following panels',
|
||||
},
|
||||
];
|
||||
|
@ -74,23 +74,11 @@ describe('AnnotationsSettings', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// we have a default build-in annotation
|
||||
dashboard = createDashboardModelFixture({
|
||||
id: 74,
|
||||
version: 7,
|
||||
annotations: {
|
||||
list: [
|
||||
{
|
||||
builtIn: 1,
|
||||
datasource: { uid: 'uid1', type: 'grafana' },
|
||||
enable: true,
|
||||
hide: true,
|
||||
iconColor: 'rgba(0, 211, 255, 1)',
|
||||
name: 'Annotations & Alerts',
|
||||
type: 'dashboard',
|
||||
showIn: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
annotations: {},
|
||||
links: [],
|
||||
});
|
||||
});
|
||||
@ -99,7 +87,8 @@ describe('AnnotationsSettings', () => {
|
||||
setup(dashboard);
|
||||
|
||||
expect(screen.queryByRole('grid')).toBeInTheDocument();
|
||||
expect(screen.getByRole('row', { name: /annotations & alerts \(built\-in\) grafana/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('row', { name: /annotations & alerts \(built-in\) -- grafana --/i })).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))
|
||||
).toBeInTheDocument();
|
||||
@ -115,7 +104,7 @@ describe('AnnotationsSettings', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('it renders the annotation names or uid if annotation doesnt exist', async () => {
|
||||
test('it renders the annotation names or uid if annotation does not exist', async () => {
|
||||
dashboard.annotations.list = [
|
||||
...dashboard.annotations.list,
|
||||
{
|
||||
|
@ -2,6 +2,7 @@ import { css } from '@emotion/css';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { AnnotationQuery, EventBus, GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { InlineField, InlineFieldRow, InlineSwitch, useStyles2 } from '@grafana/ui';
|
||||
import { LoadingIndicator } from '@grafana/ui/src/components/PanelChrome/LoadingIndicator';
|
||||
|
||||
@ -44,8 +45,17 @@ export const AnnotationPicker = ({ annotation, events, onEnabledChanged }: Annot
|
||||
return (
|
||||
<div key={annotation.name} className={styles.annotation}>
|
||||
<InlineFieldRow>
|
||||
<InlineField label={annotation.name} disabled={loading}>
|
||||
<InlineSwitch value={annotation.enable} onChange={() => onEnabledChanged(annotation)} disabled={loading} />
|
||||
<InlineField
|
||||
label={annotation.name}
|
||||
disabled={loading}
|
||||
data-testid={selectors.pages.Dashboard.SubMenu.Annotations.annotationLabel(annotation.name)}
|
||||
>
|
||||
<InlineSwitch
|
||||
value={annotation.enable}
|
||||
onChange={() => onEnabledChanged(annotation)}
|
||||
disabled={loading}
|
||||
data-testid={selectors.pages.Dashboard.SubMenu.Annotations.annotationToggle(annotation.name)}
|
||||
/>
|
||||
</InlineField>
|
||||
<div className={styles.indicator}>
|
||||
<LoadingIndicator loading={loading} onCancel={onCancel} />
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { AnnotationQuery, DataQuery, EventBus } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { AnnotationPicker } from './AnnotationPicker';
|
||||
|
||||
@ -21,7 +22,7 @@ export const Annotations = ({ annotations, onAnnotationChanged, events }: Props)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testId={selectors.pages.Dashboard.SubMenu.Annotations.annotationsWrapper}>
|
||||
{visibleAnnotations.map((annotation) => (
|
||||
<AnnotationPicker
|
||||
events={events}
|
||||
@ -30,6 +31,6 @@ export const Annotations = ({ annotations, onAnnotationChanged, events }: Props)
|
||||
key={annotation.name}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2016,8 +2016,8 @@ describe('DashboardModel', () => {
|
||||
},
|
||||
annotations: {
|
||||
list: [
|
||||
// @ts-expect-error
|
||||
{
|
||||
// @ts-expect-error
|
||||
datasource: null,
|
||||
},
|
||||
{
|
||||
|
@ -318,13 +318,11 @@ describe('DashboardModel', () => {
|
||||
list: [
|
||||
{
|
||||
datasource: { uid: 'fake-uid', type: 'prometheus' },
|
||||
showIn: 0,
|
||||
name: 'Fake annotation',
|
||||
type: 'dashboard',
|
||||
iconColor: 'rgba(0, 211, 255, 1)',
|
||||
enable: true,
|
||||
hide: false,
|
||||
builtIn: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -46,13 +46,12 @@ export function createPanelJSONFixture(panelInput: Partial<Panel | GraphPanel |
|
||||
}
|
||||
|
||||
export function createAnnotationJSONFixture(annotationInput: Partial<AnnotationQuery>): AnnotationQuery {
|
||||
// @ts-expect-error
|
||||
return {
|
||||
builtIn: 0, // ??
|
||||
datasource: {
|
||||
type: 'foo',
|
||||
uid: 'bar',
|
||||
},
|
||||
showIn: 2,
|
||||
enable: true,
|
||||
type: 'anno',
|
||||
...annotationInput,
|
||||
|
@ -44,8 +44,31 @@ function notifyWithError(title: string, err: any) {
|
||||
}
|
||||
|
||||
export function getAnnotationsByPanelId(annotations: AnnotationEvent[], panelId?: number) {
|
||||
if (panelId == null) {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
return annotations.filter((item) => {
|
||||
if (panelId !== undefined && item.panelId && item.source?.type === 'dashboard') {
|
||||
let source: AnnotationQuery;
|
||||
source = item.source;
|
||||
if (!source) {
|
||||
return true; // should not happen
|
||||
}
|
||||
|
||||
// generic panel filtering
|
||||
if (source.filter) {
|
||||
const includes = (source.filter.ids ?? []).includes(panelId);
|
||||
if (source.filter.exclude) {
|
||||
if (includes) {
|
||||
return false;
|
||||
}
|
||||
} else if (!includes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// this is valid for the main 'grafana' datasource
|
||||
if (item.panelId && item.source.type === 'dashboard') {
|
||||
return item.panelId === panelId;
|
||||
}
|
||||
return true;
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceInstanceSettings,
|
||||
DataSourceRef,
|
||||
isValidLiveChannelAddress,
|
||||
MutableDataFrame,
|
||||
parseLiveChannelAddress,
|
||||
@ -25,6 +24,7 @@ import {
|
||||
getTemplateSrv,
|
||||
StreamingFrameOptions,
|
||||
} from '@grafana/runtime';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { migrateDatasourceNameToRef } from 'app/features/dashboard/state/DashboardMigrator';
|
||||
|
||||
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
|
||||
@ -61,7 +61,10 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
|
||||
datasource = anno.datasource as DataSourceRef;
|
||||
}
|
||||
|
||||
return { ...anno, refId: anno.name, queryType: GrafanaQueryType.Annotations, datasource };
|
||||
// Filter from streaming query conflicts with filter from annotations
|
||||
const { filter, ...rest } = anno;
|
||||
|
||||
return { ...rest, refId: anno.name, queryType: GrafanaQueryType.Annotations, datasource };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DataQuery, DataFrameJSON } from '@grafana/data';
|
||||
import { DataFrameJSON } from '@grafana/data';
|
||||
import { LiveDataFilter } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { SearchQuery } from 'app/features/search/service';
|
||||
|
||||
//----------------------------------------------
|
||||
|
@ -35,7 +35,7 @@ export class EventEditorCtrl {
|
||||
|
||||
canDelete(): boolean {
|
||||
if (contextSrv.accessControlEnabled()) {
|
||||
if (this.event.source.type === 'dashboard') {
|
||||
if (this.event.source?.type === 'dashboard') {
|
||||
return !!this.panelCtrl.dashboard.meta.annotationsPermissions?.dashboard.canDelete;
|
||||
}
|
||||
return !!this.panelCtrl.dashboard.meta.annotationsPermissions?.organization.canDelete;
|
||||
|
@ -3,6 +3,7 @@ import React, { HTMLAttributes, useCallback, useRef, useState } from 'react';
|
||||
import { usePopper } from 'react-popper';
|
||||
|
||||
import { GrafanaTheme2, dateTimeFormat, systemDateFormats, TimeZone } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Portal, useStyles2, usePanelContext } from '@grafana/ui';
|
||||
import { getTooltipContainerStyles } from '@grafana/ui/src/themes/mixins';
|
||||
|
||||
@ -127,6 +128,7 @@ export function AnnotationMarker({ annotation, timeZone, width }: Props) {
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={!isRegionAnnotation ? styles.markerWrapper : undefined}
|
||||
data-testId={selectors.pages.SoloPanel.Annotations.marker}
|
||||
>
|
||||
{marker}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user