Merge remote-tracking branch 'origin/main' into resource-store

This commit is contained in:
Ryan McKinley
2024-06-12 00:48:13 +03:00
69 changed files with 3580 additions and 2156 deletions
+3 -4
View File
@@ -3891,9 +3891,7 @@ exports[`better eslint`] = {
],
"public/app/features/explore/Logs/Logs.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/explore/Logs/LogsFeedback.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
@@ -4653,7 +4651,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "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"]
],
"public/app/features/logs/components/InfiniteScroll.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
+4
View File
@@ -1265,6 +1265,10 @@ ha_label =
# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
ha_gossip_interval = 200ms
# Length of time to attempt to reconnect to a lost peer. Recommended to be short (<15m) when Grafana is running in a Kubernetes cluster.
# The string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
ha_reconnect_timeout = 6h
# The interval between gossip full state syncs. Setting this interval lower (more frequent) will increase convergence speeds
# across larger clusters at the expense of increased bandwidth usage.
# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
+4
View File
@@ -1251,6 +1251,10 @@
# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
;ha_gossip_interval = "200ms"
# Length of time to attempt to reconnect to a lost peer. Recommended to be short (<15m) when Grafana is running in a Kubernetes cluster.
# The string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
;ha_reconnect_timeout = 6h
# The interval between gossip full state syncs. Setting this interval lower (more frequent) will increase convergence speeds
# across larger clusters at the expense of increased bandwidth usage.
# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
@@ -0,0 +1,414 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 271,
"links": [],
"panels": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 0,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 6,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"barRadius": 0,
"barWidth": 1,
"fullHighlight": false,
"groupWidth": 0.82,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "auto",
"stacking": "none",
"text": {},
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"targets": [
{
"csvContent": "Name,Stat1\nStockholm, 10\nNew York, 19\nLondon, 10\nNegative, 15\nLong value, 15",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "One series",
"type": "barchart"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 0,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 6,
"x": 6,
"y": 0
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "auto",
"stacking": "none",
"text": {},
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"targets": [
{
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, 5\nLong value, 15,10",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "Two series",
"type": "barchart"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 0,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 6,
"x": 0,
"y": 11
},
"id": 3,
"options": {
"barRadius": 0,
"barWidth": 1,
"fullHighlight": false,
"groupWidth": 0.82,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "auto",
"stacking": "normal",
"text": {},
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"targets": [
{
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, 5\nLong value, 15,10",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "Two series",
"type": "barchart"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 0,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 6,
"x": 6,
"y": 11
},
"id": 4,
"options": {
"barRadius": 0,
"barWidth": 0.7,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "auto",
"stacking": "normal",
"text": {},
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"targets": [
{
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, 5\nLong value, 15,10",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "Two series",
"type": "barchart"
}
],
"schemaVersion": 39,
"tags": [
"gdev",
"panel-tests",
"barchart",
"graph-ng"
],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "BarChart - Panel Tests - Series toggle / bar widths",
"uid": "adoero0hbka9sf",
"version": 4,
"weekStart": ""
}
+1
View File
@@ -17,6 +17,7 @@
"bar_gauge_demo": (import '../dev-dashboards/panel-bargauge/bar_gauge_demo.json'),
"barchart-autosizing": (import '../dev-dashboards/panel-barchart/barchart-autosizing.json'),
"barchart-label-rotation-skipping": (import '../dev-dashboards/panel-barchart/barchart-label-rotation-skipping.json'),
"barchart-series-toggle": (import '../dev-dashboards/panel-barchart/barchart-series-toggle.json'),
"barchart-thresholds-mappings": (import '../dev-dashboards/panel-barchart/barchart-thresholds-mappings.json'),
"barchart-tooltips-legends": (import '../dev-dashboards/panel-barchart/barchart-tooltips-legends.json'),
"candlestick": (import '../dev-dashboards/panel-candlestick/candlestick.json'),
+4 -4
View File
@@ -16,13 +16,13 @@ labels:
- enterprise
- oss
menuTitle: Grafana documentation
title: Grafana OSS and Enterprise documentation
title: Grafana OSS and Enterprise
hero:
title: Grafana OSS and Enterprise documentation
title: Grafana OSS and Enterprise
level: 1
width: 110
width: 100
image: /media/docs/grafana-cloud/infrastructure/grafanalogo.svg
height: 110
height: 100
description: Query, visualize, alert on, and explore your metrics, logs, and traces wherever they are stored.
cards:
title_class: pt-0 lh-1
@@ -147,4 +147,5 @@ The following metrics can be used for meta monitoring, exposed by the `/metrics`
ha_peers = "grafana-alerting.grafana:9094"
ha_advertise_address = "${POD_IP}:9094"
ha_peer_timeout = 15s
ha_reconnect_timeout = 2m
```
@@ -19,7 +19,7 @@ To learn more about Grafana Enterprise, refer to [our product page](/enterprise)
## Enterprise features in Grafana Cloud
Many Grafana Enterprise features are also available in [Grafana Cloud](/docs/grafana-cloud) Free, Pro, and Advanced accounts. For details, refer to [the Grafana Cloud features table](/pricing/#featuresTable).
Many Grafana Enterprise features are also available in [Grafana Cloud](/docs/grafana-cloud) Free, Pro, and Advanced accounts. For details, refer to [Grafana Cloud pricing](/pricing/#featuresTable).
To migrate to Grafana Cloud, refer to [Migrate from Grafana Enterprise to Grafana Cloud](/docs/grafana-cloud/account-management/e2c-guide/).
@@ -79,12 +79,10 @@ With a Grafana Enterprise license, you also get access to premium data sources,
- [Azure Devops](/grafana/plugins/grafana-azuredevops-datasource)
- [Databricks](/grafana/plugins/grafana-databricks-datasource)
- [DataDog](/grafana/plugins/grafana-datadog-datasource)
- [Databricks](/grafana/plugins/grafana-databricks-datasource/)
- [Dynatrace](/grafana/plugins/grafana-dynatrace-datasource)
- [Gitlab](/grafana/plugins/grafana-gitlab-datasource)
- [Honeycomb](/grafana/plugins/grafana-honeycomb-datasource)
- [Jira](/grafana/plugins/grafana-jira-datasource)
- [k6 Cloud App](/grafana/plugins/grafana-k6-app)
- [MongoDB](/grafana/plugins/grafana-mongodb-datasource)
- [New Relic](/grafana/plugins/grafana-newrelic-datasource)
- [Oracle Database](/grafana/plugins/grafana-oracle-datasource)
@@ -28,12 +28,14 @@ refs:
# Alert list
Alert lists allow you to display a list of important alerts that you want to track. You can configure the alert list to show the current state of your alert, such as firing, pending, or normal. Learn more about alerts in [Grafana Alerting overview][].
Alert lists allow you to display a list of important alerts that you want to track. You can configure the alert list to show the current state of your alert, such as firing, pending, or normal. Learn more about alerts in [Grafana Alerting overview](ref:grafana-alerting-overview).
{{< figure src="/static/img/docs/alert-list-panel/alert-list-panel.png" max-width="850px" alt="An alert list visualization" >}}
On each dashboard load, this visualization queries the alert list, always providing the most up-to-date results.
{{< docs/play title="Alert List" url="https://play.grafana.org/d/bdodlcyou483ke/" >}}
## Configure an alert list
Once you’ve created a [dashboard](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/build-dashboards/create-dashboard/), the following video shows you how to configure an alert list visualization:
@@ -30,6 +30,8 @@ In version 8.5, we discontinued the "Use Proxy" option for Grafana news visualiz
You can use the news visualization to provide regular news and updates to your users.
{{< docs/play title="News Panel" url="https://play.grafana.org/d/cdodkwspaaa68b/" >}}
## Configure a news visualization
Once you’ve created a [dashboard](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/build-dashboards/create-dashboard/), enter the URL of an RSS in the [URL](#url) field in the **News** section. This visualization type doesn't accept any other queries, and you shouldn't expect to be able to filter or query the RSS feed data in any way using this visualization.
@@ -29,6 +29,8 @@ The following video provides beginner steps for creating node panel visualizatio
{{< youtube id="VrvsMkRwoKw" >}}
{{< docs/play title="Node graph panel" url="https://play.grafana.org/d/bdodfbi3d57uoe/" >}}
## Data requirements
A node graph requires a specific shape of the data to be able to display its nodes and edges. This means not every data source or query can be visualized with this graph. If you want to use this as a data source developer see the section about data API.
@@ -65,6 +65,8 @@ For more information about traces and how to use them, refer to the following do
{{< figure src="/static/img/docs/explore/trace-view-9-4.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the trace view" >}}
{{< docs/play title="Traces Panel" url="https://play.grafana.org/d/edodkfmj0tce8f/" >}}
## Add a panel with tracing visualizations
Once you have tracing data available in your Grafana stack, you can add tracing panels to your Grafana dashboards.
@@ -1635,6 +1635,12 @@ across cluster more quickly at the expense of increased bandwidth usage. The def
The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
### ha_reconnect_timeout
Length of time to attempt to reconnect to a lost peer. When running Grafana in a Kubernetes cluster, set this duration to less than `15m`.
The string is a possibly signed sequence of decimal numbers followed by a unit suffix (ms, s, m, h, d), such as `30s` or `1m`.
### ha_push_pull_interval
The interval between gossip full state syncs. Setting this interval lower (more frequent) will increase convergence speeds
@@ -23,6 +23,8 @@ weight: -42
Welcome to Grafana 11.0! This release contains some major improvements: most notably, the ability to explore your Prometheus metrics and Loki logs without writing any PromQL or LogQL, using Explore Metrics and Explore Logs. The dashboard experience is better than ever with edit mode for dashboards, AI-generated dashboard names and descriptions, and general availability for subfolders. You can also take advantage of improvements to the canvas and table visualizations, new transformations, a revamp of the Alert Rule page, and more.
{{< youtube id="96ZXShRyMB8" >}}
For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). For the specific steps we recommend when you upgrade to v11.0, check out our [Upgrade Guide](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/upgrade-guide/upgrade-v11.0/).
## Breaking changes
+6 -2
View File
@@ -33,10 +33,12 @@ describe('Keyboard shortcuts', () => {
zone: 'Browser',
});
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
let expectedRange = `Time range selected: 2024-06-05 10:05:00 to 2024-06-05 10:06:00`;
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
cy.get('body').type('{ctrl}z');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
let expectedRange = `Time range selected: 2024-06-05 10:03:30 to 2024-06-05 10:07:30`;
expectedRange = `Time range selected: 2024-06-05 10:03:30 to 2024-06-05 10:07:30`;
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
});
@@ -51,11 +53,13 @@ describe('Keyboard shortcuts', () => {
zone: 'Browser',
});
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
let expectedRange = `Time range selected: 2024-06-05 10:05:00 to 2024-06-05 10:06:00`;
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
cy.log('Trying one shift-left');
cy.get('body').type('t{leftarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
let expectedRange = `Time range selected: 2024-06-05 10:04:00 to 2024-06-05 10:05:00`; // 1 min back
expectedRange = `Time range selected: 2024-06-05 10:04:00 to 2024-06-05 10:05:00`; // 1 min back
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
cy.log('Trying two shift-lefts');
@@ -15,6 +15,14 @@ export const pluginVersion = "11.1.0-pre";
export interface Options {
dedupStrategy: common.LogsDedupStrategy;
enableLogDetails: boolean;
isFilterLabelActive?: unknown;
/**
* TODO: figure out how to define callbacks
*/
onClickFilterLabel?: unknown;
onClickFilterOutLabel?: unknown;
onClickFilterOutString?: unknown;
onClickFilterString?: unknown;
prettifyLogMessage: boolean;
showCommonLabels: boolean;
showLabels: boolean;
+1 -1
View File
@@ -63,7 +63,7 @@ var DependencyError = errutil.NewBase(
depErrStr,
errutil.WithPublic(depErrStr))
func makeDependencyError(refID, depRefID string) error {
func MakeDependencyError(refID, depRefID string) error {
data := errutil.TemplateData{
Public: map[string]interface{}{
"refId": refID,
+1 -1
View File
@@ -90,7 +90,7 @@ func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (m
if res, ok := vars[neededVar]; ok {
if res.Error != nil {
errResult := mathexp.Results{
Error: makeDependencyError(node.RefID(), neededVar),
Error: MakeDependencyError(node.RefID(), neededVar),
}
vars[node.RefID()] = errResult
hasDepError = true
+332
View File
@@ -0,0 +1,332 @@
#name,created,deleted,hash,author
newNavigation,2022-01-26T17:44:20Z,2022-06-16T09:48:38Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
trimDefaults,2022-01-26T17:44:20Z,2023-11-02T15:35:14Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
dashboardPreviews,2022-01-26T17:44:20Z,2023-04-13T17:42:24Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
live-config,2022-01-26T17:44:20Z,2023-02-03T21:21:48Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
accesscontrol,2022-01-26T17:44:20Z,2022-05-16T10:45:41Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
tempoServiceGraph,2022-01-26T17:44:20Z,2022-07-19T07:00:58Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
httpclientprovider_azure_auth,2022-01-26T17:44:20Z,2022-05-30T15:43:32Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
tempoBackendSearch,2022-01-26T17:44:20Z,2022-06-01T17:32:10Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
showFeatureFlagsInUI,2022-01-26T17:44:20Z,2023-02-09T00:01:34Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
queryOverLive,2022-01-26T17:44:20Z,,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
validatedQueries,2022-01-26T17:44:20Z,2022-05-16T21:17:05Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
fullRangeLogsVolume,2022-01-26T17:44:20Z,2022-02-15T08:05:03Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
prometheus_azure_auth,2022-01-26T17:44:20Z,2022-08-11T14:12:57Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
live-service-web-worker,2022-01-26T17:44:20Z,,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
envelopeEncryption,2022-01-26T17:44:20Z,2022-05-24T08:34:47Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
database_metrics,2022-01-26T17:44:20Z,2023-04-28T13:19:06Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
live-pipeline,2022-01-26T17:44:20Z,2023-03-22T18:09:44Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
service-accounts,2022-01-26T17:44:20Z,2022-04-21T09:41:37Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
tempoSearch,2022-01-26T17:44:20Z,2022-06-01T17:32:10Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
disable_http_request_histogram,2022-01-26T17:44:20Z,2022-06-01T12:33:59Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley
featureHighlights,2022-02-03T11:53:23Z,,a79c048344bddff7a868b040d9a08953917480f9,Alex Khomenko
lokiBackendMode,2022-02-07T07:43:48Z,2022-06-08T06:14:34Z,560c77390550e12e8c0be507c00f27cde0aa31e5,Gábor Farkas
swaggerUi,2022-02-08T12:38:43Z,2023-03-01T14:36:37Z,35fe58de374003bb4b077a878cc47ffc0a9d27b5,Sofia Papagiannaki
influxdbBackendMigration,2022-02-09T18:26:16Z,2023-01-17T14:11:26Z,10232c78577520ce7b3fc6bba86d2e48ee5bab88,Joey Tawadrous
dashboardPreviewsScheduler,2022-02-10T18:45:00Z,2022-04-24T21:55:10Z,0276b029fc656e61763f6ae045ad7826380a43fc,Artur Wierzbicki
migrationLocking,2022-02-15T16:54:27Z,2024-03-22T19:22:29Z,d718ee191834b3084b2e8c8c6cac473bd9b97681,Sofia Papagiannaki
panelTitleSearch,2022-02-15T18:26:03Z,,d665306ad1c2abe515c7ba48350904f749eafc18,Ryan McKinley
dashboardPreviewsAdmin,2022-02-17T08:34:07Z,2023-02-03T21:45:10Z,3e53a08090ad8ac61d7d316b97a93a7a5ea63a57,Artur Wierzbicki
annotationComments,2022-02-22T07:47:42Z,2023-03-11T12:28:12Z,28c30a34adbe94d0f08acdba3c1605143dce6c6c,Alexander Emelin
dashboardComments,2022-02-22T07:47:42Z,2023-03-11T12:28:12Z,28c30a34adbe94d0f08acdba3c1605143dce6c6c,Alexander Emelin
lokiLive,2022-03-01T22:46:52Z,2023-06-19T10:03:51Z,796bc27f75d52148d5b15cc8c4901276c344df2d,Ryan McKinley
fileStoreApi,2022-03-03T06:53:26Z,2022-03-11T18:08:19Z,a8b90d9a2524765c49923c48a7fcf0025c85b733,Artur Wierzbicki
azureMonitorResourcePickerForMetrics,2022-03-14T19:07:45Z,2023-01-30T16:19:03Z,275f33cf37bb4221ef120310a87c17d2e0ff5f35,Sarah Zinger
storageLocalUpload,2022-03-17T17:19:23Z,2022-07-18T17:44:42Z,1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f,Ryan McKinley
storage,2022-03-17T17:19:23Z,,1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f,Ryan McKinley
saveDashboardDrawer,2022-03-30T17:07:41Z,2022-05-02T16:29:22Z,edf384c730a448a5e90b21bbbce49bc0e70e8197,Ryan McKinley
accesscontrol-builtins,2022-03-31T09:40:57Z,2022-05-19T07:29:36Z,0d87de153a2b8406d03efe0cf43b5a926ce7acab,Gabriel MABILLE
alertProvisioning,2022-04-01T06:32:00Z,2022-06-05T05:45:36Z,b8e277ee4c070b64ba4cf024b18c460626d1a2d3,Alexander Weaver
explore2Dashboard,2022-04-07T08:26:01Z,2022-11-07T16:06:40Z,ca286a238d1e9ce1e5357fde8148476a9d618cee,Giordano Ricci
publicDashboards,2022-04-07T18:30:19Z,,4a4d87dbdcaf4dca753bcf29205ac036b6c59862,Jeff Levin
persistNotifications,2022-04-20T09:42:32Z,2022-05-26T11:03:04Z,c48d8d1d488e0ce5870c54eb5828660081743108,kay delaney
serviceAccounts,2022-04-21T09:41:37Z,2022-06-27T08:22:49Z,8677552dda7b63f5d44df6fe51ca8a28726c5a5c,Eric Leijonmarck
commandPalette,2022-04-21T21:50:34Z,2023-02-02T14:44:21Z,3e7db088acd1da32ea489b0756cdf4e15ec2da69,Kristina
export,2022-04-25T23:59:18Z,2023-03-01T17:42:53Z,e0aeb83786731769e870d79c0d8a21ef2c33b073,Ryan McKinley
savedItems,2022-04-26T10:27:01Z,2022-06-27T14:41:00Z,e420252d45cd48fdcf5cc68ad84f89e36d5c936c,Ashley Harrison
tracing,2022-04-26T13:31:27Z,2023-03-21T09:35:21Z,3b4d237ade03e67a971ca8899e8e0371e1d83f2a,kay delaney
cloudWatchDynamicLabels,2022-04-29T09:43:04Z,2023-05-02T08:48:17Z,7bb4f5cd9baff81b354e4bb65fd4b6141e3488f6,Shirley
datasourceQueryMultiStatus,2022-05-03T16:02:20Z,,4ecd57f49c75d72927be62aba23a15dca02f8eb6,Will Browne
azureMonitorExperimentalUI,2022-05-04T13:54:09Z,2022-07-14T13:07:31Z,b2644de6c8a08181cbe0a9f545393bd42753477c,Adam Simpson
traceToMetrics,2022-05-05T20:46:18Z,2024-02-22T13:30:41Z,c1b5ea3e54169c7a4bbd58bd8be2c4c192a093c0,Connor Lindsey
prometheusStreamingJSONParser,2022-05-13T18:28:54Z,2022-11-11T16:53:12Z,87e8521591534bcfd8a7c78fbd39b226c13a99b2,Todd Treece
validateDashboardsOnSave,2022-05-22T00:44:12Z,2023-09-06T15:25:44Z,a3402641d6cac01208179126dcbd2256ad500cd8,sam boyer
disableEnvelopeEncryption,2022-05-24T08:34:47Z,,3e4b4dba46e5310015c7de5c3de1f3fae5332cb8,Joan López de la Franca Beltran
prometheusWideSeries,2022-05-24T20:17:11Z,2023-08-24T14:47:19Z,94b9c524a81df2085ad769229ca09cfd1923a8e9,Todd Treece
prometheusAzureOverrideAudience,2022-05-30T15:43:32Z,2023-07-16T21:30:14Z,2b83cf46186f745e356a24e4c78a1fbc24b3051e,Sergey Kostrukov
canvasPanelNesting,2022-05-31T19:03:34Z,,e18e8002c4dda17e95cb4f04977a6aefcc5984a4,Adela Almasan
cloudMonitoringExperimentalUI,2022-06-09T13:56:32Z,2022-09-15T14:12:26Z,615132002044e56074be63c5b597b9982e0a2a21,Kevin Yu
tempoApmTable,2022-06-09T16:56:15Z,2023-02-01T15:56:34Z,4ed7ff2ed13f2b5ec79f0946670c945edc902d7b,Joey Tawadrous
logRequestsInstrumentedAsUnknown,2022-06-10T08:56:55Z,,2d6e69226be20a7f001c7792bda8df68d90287c6,Carl Bergquist
dataConnectionsConsole,2022-06-10T10:13:31Z,2024-02-29T09:29:41Z,9a85a2e4410266270a1cd59db1937c7ba248966e,Levente Balogh
internationalization,2022-06-10T11:02:52Z,2023-06-02T08:24:40Z,9a62849dc39e960640b5762cc5eace1baf29c0ee,Josh Hunt
autoMigrateGraphPanels,2022-06-11T00:12:56Z,2023-03-23T04:02:36Z,ae8dd737704bb6232c52e19090e593d9165f5e77,Ryan McKinley
lokiDataframeApi,2022-06-13T06:33:46Z,2023-04-13T13:22:09Z,8fd9cb48548313cf43ebcf5dcfa0daad0bfdfe0c,Gábor Farkas
topnav,2022-06-20T14:25:43Z,,3c3293df78344a782fb65e5f977fd148daa597ce,Torkel Ödegaard
customBranding,2022-06-22T15:05:52Z,2022-10-05T12:07:35Z,405df77e3e6abcf5264ba192abe33630bd64da20,Tania
useLegacyHeatmapPanel,2022-06-23T18:48:28Z,2022-11-23T18:46:21Z,dd5a3b77472884035de13ddd441f41d0dd007e4b,Ryan McKinley
scenes,2022-07-07T06:53:02Z,,935334cbdabef8b0516bfe6c8ed45dd063cce7e9,Torkel Ödegaard
disableSecretsCompatibility,2022-07-12T20:27:37Z,,2d8a91a8461098f83aa512c867aff5af17b7893e,Guilherme Caulada
dashboardsFromStorage,2022-07-14T22:36:17Z,2023-03-20T16:36:49Z,da1701ce576ab26506d6256567b73a597043623a,Ryan McKinley
exploreMixedDatasource,2022-07-27T14:40:59Z,2022-07-27T15:17:31Z,e2258120e742b31ecde50e8de93544220a0762a3,Kristina
prometheusStreamingJSONParserTest,2022-07-28T12:26:51Z,2022-11-07T18:20:00Z,d0e548c3e53ffdbc29f35e055d52aa63cca310db,Andrej Ocenas
fullstoryUserTracking,2022-08-16T08:32:00Z,2022-08-16T10:45:14Z,961479b1115d39f3918b60c49a48841916939f03,Ivan Ortega Alba
traceqlEditor,2022-08-24T16:57:59Z,2022-12-13T13:27:45Z,c8f2148f7504b393b8f9b32fe19e7f519fa66793,Andre Pereira
athenaAsyncQueryDataSupport,2022-09-05T15:39:45Z,2024-03-20T14:14:21Z,34fe7a1119c0f649d3e9df866353438ff2177fdc,Kevin Yu
redshiftAsyncQueryDataSupport,2022-09-05T15:39:45Z,2024-03-20T14:14:21Z,34fe7a1119c0f649d3e9df866353438ff2177fdc,Kevin Yu
increaseInMemDatabaseQueryCache,2022-09-12T07:50:54Z,2023-02-06T21:01:04Z,72ae4a5aa368d18793f778683e92ae4a3ac7a1db,Carl Bergquist
correlations,2022-09-16T13:14:27Z,,9b4cdfe6526a290e5708a8c0760d50efda607d5c,Piotr Jamróz
grpcServer,2022-09-26T20:25:34Z,,55aae797441d2beadc911e390f14c04bb3f101da,Alexander Emelin
alertingBigTransactions,2022-10-06T06:22:58Z,2023-04-06T16:06:25Z,b476ae62fba9b94dd272409c521812108750bf50,Joe Blubaugh
lokiMonacoEditor,2022-10-06T14:35:30Z,2023-02-06T10:18:01Z,729ce8bb72a5db53fbeb1b5856557652bb175c7a,Matias Chomicki
objectStore,2022-10-06T19:48:53Z,2022-11-30T22:52:15Z,609abf00d1c6bd065f163e06882aa2f09dc0fcb5,Ryan McKinley
flameGraph,2022-10-07T10:39:14Z,2023-03-01T11:32:39Z,74c809f54459d235f4d3e9b1c117f291cbf16339,Joey Tawadrous
queryLibrary,2022-10-07T18:31:45Z,2023-03-20T16:00:14Z,bf264d2f76a1efb902b41791e110077d5c3a5557,Artur Wierzbicki
newPanelChromeUI,2022-10-10T15:03:16Z,2023-09-12T08:54:41Z,129a5a29e78853bb1427c8dc7473125ba2a35981,Polina Boneva
mysqlAnsiQuotes,2022-10-12T11:43:35Z,,80ede174dd6e343722cc708ebc4c5f21fb9e90af,ying-jeanne
showDashboardValidationWarnings,2022-10-14T13:51:05Z,,2e16d5499e1cf29e67db5031feda09407f69550c,Josh Hunt
interFont,2022-10-15T14:22:33Z,2022-12-01T11:59:37Z,9f5e691994c9db15f5984b196ab55fd7213b7f72,Torkel Ödegaard
accessControlOnCall,2022-10-19T16:10:09Z,,717bd4a6c051d1447c9e35385a4ba176dd391c65,Gabriel MABILLE
newDBLibrary,2022-10-26T01:20:41Z,2023-11-14T14:51:35Z,a3acfb1a48126119fd4913efbb572de162264e92,Ryan McKinley
nestedFolders,2022-10-26T14:15:14Z,,b346ae03105af606521bbefec16722d590378646,Kristin Laemmert
datasourceLogger,2022-11-02T13:51:51Z,2023-02-07T11:49:16Z,06705a49e236dc3ab3b35db51b7bad8625e4866f,Carl Bergquist
promQueryBuilder,2022-11-03T17:34:01Z,2022-12-19T13:52:06Z,857e545c5ac71722c8b2d3d27621dfb453dbb8ff,Ryan McKinley
elasticsearchBackendMigration,2022-11-10T15:35:15Z,2023-04-12T12:20:43Z,261d620f1c46eb43282cc444ffb4633b77af4283,Ivana Huckova
prometheusBufferedClient,2022-11-11T16:53:12Z,2022-12-12T17:05:55Z,1c5039085bdf9252d0aa1666675f5313589c1703,ismail simsek
accessTokenExpirationCheck,2022-11-14T15:47:46Z,2023-12-15T12:20:17Z,4915d21c25891dae2ee7a4eec4fe0dd2659d614d,Misi
cloudWatchCrossAccountQuerying,2022-11-28T11:39:12Z,,254577ba568d8010dcc96446e788866d618c96c8,Erik Sundell
authnService,2022-11-29T09:57:47Z,2023-06-07T06:57:41Z,17ec4089dc75da0e54edf60a754a0fc9291275e3,Karl Persson
secureSocksDatasourceProxy,2022-11-30T05:50:59Z,2023-04-18T17:11:23Z,6805c951e94c626401770ed468c6afb462093c0d,Stephanie Hingtgen
entityStore,2022-11-30T22:52:15Z,2023-12-06T20:21:21Z,14a080ec122f8ff3f656f44a46683f5161c15e14,Ryan McKinley
sessionRemoteCache,2022-12-07T09:55:48Z,2023-02-06T12:51:12Z,df4f0343e587695f37fa7a6de04cc884af4df284,Jo
returnUnameHeader,2022-12-07T12:14:53Z,2023-02-07T09:04:20Z,1b676d0d49460572352af4639274a4e7fe03fc9b,Carl Bergquist
datasourceOnboarding,2022-12-07T23:29:38Z,2023-05-17T12:22:40Z,955bf55c6a6a710a2fd7ebdbdac2d9a826858eaf,kay delaney
k8s,2022-12-13T15:41:16Z,2023-04-26T13:52:13Z,cf055ab4ecda9db9427ae09560aa26553433a5c0,Ryan McKinley
alertingBacktesting,2022-12-14T14:44:14Z,,ad09feed837737c188e37fc6adfead7b8a45715e,Yuri Tseretyan
disablePrometheusExemplarSampling,2022-12-19T15:00:15Z,2024-01-30T16:00:04Z,f67b8fe0dc2a2d860e71887a4d2e6e25da80bd8f,Ludovic Viaud
supportBundles,2022-12-20T10:13:37Z,2023-02-10T09:12:04Z,2c7410c87d81b8a644e738fee2bb6f34355111c6,Jo
publicDashboardsEmailSharing,2023-01-03T19:45:15Z,,f0ee3ac80ae2af695c56e709af435c155a2fb33d,owensmallwood
alertingNoNormalState,2023-01-13T23:29:29Z,,9d57b1c72eb817c5fe1b43ad7bc9706a264b2da0,Yuri Tseretyan
azureMultipleResourcePicker,2023-01-20T15:29:23Z,2023-01-30T16:19:03Z,b1efd911c1f7bfabe1b8231b059470c72dce4712,Andres Martinez Gotor
editPanelCSVDragAndDrop,2023-01-24T09:43:44Z,,4167214e3535f41c85004c6aca86fcb9ae8956dd,Zoltán Bedi
topNavCommandPalette,2023-01-24T12:41:09Z,2023-02-24T12:14:53Z,88347caf5fcc99c21c8a07367b9e846947367636,Josh Hunt
k8sDashboards,2023-01-25T19:10:16Z,2023-02-09T17:54:00Z,4965cf2edae6eae8e341e6c93c3a1aa56417efb3,Ryan McKinley
logsContextDatasourceUi,2023-01-27T14:12:01Z,,7c02d9bb8a864aab35c676f1a44c6c5be9eb6c09,Sven Grossmann
logsSampleInExplore,2023-01-27T16:30:25Z,2023-07-24T12:24:43Z,d5294eb8fa71ba62546d00ebce7b9376ab6f2b1a,Ivana Huckova
apiserver,2023-02-01T20:28:19Z,2023-02-09T17:54:00Z,d4f4a83574dd03a5419fa890b916d09d03cb9acd,Ryan McKinley
lokiQuerySplitting,2023-02-09T17:27:02Z,,94241f6676cf36824a180c11c2824ad41c81a470,Matias Chomicki
individualCookiePreferences,2023-02-21T10:19:07Z,,0caacb3333152015bf0369f54315a26ff6f60f88,Emil Tullstedt
newTraceView,2023-02-28T15:41:40Z,2023-04-21T10:31:24Z,82bcfb492867138bcc4744f88d36eb081c1b6811,Joey
drawerDataSourcePicker,2023-03-01T10:26:19Z,2023-04-14T11:01:10Z,dc1600ff14acc3deecc5de02f6584dcf20d3c8b1,Oscar Kilhed
traceqlSearch,2023-03-06T16:31:08Z,2023-07-24T15:26:10Z,fd37ff29b57520036fdc02f8a93a7d74770fd153,Andre Pereira
prometheusMetricEncyclopedia,2023-03-07T18:41:05Z,,9b6e531549e145d1a1a0ede870a6163c69b0bfd5,Brendan O'Handley
timeSeriesTable,2023-03-10T12:41:06Z,2023-10-13T08:00:42Z,548a5054ad8fa5c199bd01e20a02fbbe558d52fe,Domas
lokiQuerySplittingConfig,2023-03-20T15:51:36Z,,68551ac9ca5b92621e6ab895c759822797bbf418,Sven Grossmann
onlyExternalOrgRoleSync,2023-03-22T17:41:59Z,2023-07-25T09:51:47Z,3cd952b8bad8d23daee10fcc303a6d19c8b6d0a0,Eric Leijonmarck
autoMigrateOldPanels,2023-03-23T04:02:36Z,,baf5a1d1411ee7e5f297aba099e735ebeee3ab83,Ryan McKinley
clientTokenRotation,2023-03-23T13:39:04Z,2024-02-16T14:03:37Z,382b24742ab8b124af072533f74027a1a103b037,Karl Persson
disableAngular,2023-03-23T15:43:45Z,,58704390269c8eeedd32c571245d5ef08f0ffb82,Ryan McKinley
disableElasticsearchBackendExploreQuery,2023-03-27T13:52:27Z,2023-04-12T12:20:43Z,f3da91f53fa32a8e870124e9514a010b97359529,Ivana Huckova
emptyDashboardPage,2023-03-28T09:42:23Z,2024-01-25T14:04:29Z,221c5efedc593fb42d87c849b3d0827e325fc6cd,Polina Boneva
prometheusDataplane,2023-03-29T15:26:32Z,,674144c8e84cb4aca3bfc448f08b88bb516d3b77,Kyle Brandt
alertStateHistoryLokiOnly,2023-03-30T18:53:21Z,,b2abb6328677a90e3f3bc8b706d22734696d02e3,Alexander Weaver
alertStateHistoryLokiPrimary,2023-03-30T18:53:21Z,,b2abb6328677a90e3f3bc8b706d22734696d02e3,Alexander Weaver
alertStateHistoryLokiSecondary,2023-03-30T18:53:21Z,,b2abb6328677a90e3f3bc8b706d22734696d02e3,Alexander Weaver
unifiedRequestLog,2023-03-31T13:38:09Z,,be9361cb9ec2f1ad0bc0cda139c56e3cd46deb16,Emil Tullstedt
prometheusResourceBrowserCache,2023-04-03T14:07:17Z,2023-10-11T13:18:56Z,96e9e807396b600c3325f4d3580eeef0710f6050,Galen Kistler
renderAuthJWT,2023-04-03T16:53:38Z,,87a0c95164074dce6a877ea3faa3c7ac0e705ad3,Joan López de la Franca Beltran
showTraceId,2023-04-05T07:13:24Z,2023-07-20T05:59:38Z,4ded937c79a38087576b3224bf6f2ba59345571e,Gábor Farkas
pyroscopeFlameGraph,2023-04-05T12:57:24Z,2023-08-02T15:01:12Z,37eaf5019751aa76568574757830d63b764bbb1f,Andrej Ocenas
dataplaneFrontendFallback,2023-04-07T21:13:19Z,,af31c77331c89c6a1f8e00747c44c3f39bb737bb,Ryan McKinley
externalServiceAuth,2023-04-11T13:21:19Z,2024-02-26T10:29:09Z,5df9e64986e8c0324b4da96de5b55e533c3990ed,Gabriel MABILLE
disableElasticsearchBackendQuerying,2023-04-12T12:20:43Z,2023-04-14T09:24:35Z,303777e682758076d6a8e1df6b82f97b3b2393a7,Ivana Huckova
disableSSEDataplane,2023-04-12T16:24:34Z,,e78be44e1a38b7afb85909b807b6a2e9e5a98830,Kyle Brandt
useCachingService,2023-04-12T16:30:33Z,2024-01-17T21:53:25Z,5626461b3c358f5c5c5c690b82977c09744d09b4,Michael Mandrus
lokiMetricDataplane,2023-04-13T13:07:08Z,,531caec60263738ed6b31878b1ec093de635e4e2,Gábor Farkas
authenticationConfigUI,2023-04-13T14:07:43Z,2023-06-14T16:58:15Z,7476219b0c2876f60bc9c6e28b22190f239e1426,Alexander Zobnin
enableElasticsearchBackendQuerying,2023-04-14T09:24:35Z,2024-06-05T15:03:29Z,f48c858ca22b3f246d1033670f14605adb13cdf3,Ivana Huckova
advancedDataSourcePicker,2023-04-14T11:01:10Z,2024-02-06T09:20:07Z,7bc31ab04b5c87f06bdb383c7f4a6195e70d53d1,Ivan Ortega Alba
opensearchDetectVersion,2023-04-14T17:59:31Z,2023-05-16T14:22:23Z,1b0cee491a06fa135936b883406a6b11e6221322,Isabella Siu
pluginsAPIManifestKey,2023-04-18T14:12:05Z,2023-06-16T09:20:30Z,98c695c68fb3f0d276da0d98b1e78186e459db50,Andres Martinez Gotor
newTraceViewHeader,2023-04-21T10:31:24Z,2023-07-19T13:31:58Z,6522bb377e1bc4b64fe0aebd7e34ec2111859e11,Joey
enableDatagridEditing,2023-04-24T14:46:31Z,,efd0e9cbea4a6c4c51337f14004f2df2f876fdb1,Victor Marin
faroDatasourceSelector,2023-05-05T00:35:10Z,,e7cbe0276e3a6f41c31dd8e1ac16ccaa90b184f3,Elliot Kirk
extraThemes,2023-05-10T13:37:04Z,,f8cf67347f069310b8cc6a2d9ff83345a81e3247,Torkel Ödegaard
dataSourcePageHeader,2023-05-23T13:18:00Z,2023-11-06T20:21:15Z,7f84e83ffee53446b4c458c29f7c1385dac09ce3,Taewoo K
alertingNotificationsPoliciesMatchingInstances,2023-05-30T13:15:22Z,2023-11-09T17:35:03Z,2f0728ac677c26b291947354058253d2e8a50bb7,Konrad Lalik
lokiPredefinedOperations,2023-06-02T10:52:36Z,,06003c98c855fcb7c1c718c3f0e5f190b644656a,Ivana Huckova
pluginsFrontendSandbox,2023-06-05T08:51:36Z,,1ed4c0382b3812eecf9722596d8a3ac922f3543d,Esteban Beltran
refactorVariablesTimeRange,2023-06-06T13:12:09Z,,07dd90b5a8866273813c6b4a5e7087cea529f908,Alexa V
sqlDatasourceDatabaseSelection,2023-06-06T16:28:52Z,,c0a1fc2cbdc1ceab4fdcb43270fd3720efe4494a,Jev Forsberg
cloudWatchLogsMonacoEditor,2023-06-12T13:49:52Z,2024-03-18T12:56:57Z,5a831d877ac3305e7dfdb54057069b41b36b40fa,Isabella Siu
recordedQueriesMulti,2023-06-14T12:34:22Z,,db75f20e53e717503fe3bf27217fedc40f4a12e1,Kyle Brandt
exploreScrollableLogsContainer,2023-06-15T11:25:34Z,2024-03-12T14:53:13Z,cda10fae525d5cf784368791e1aaa1de2ce88f4b,Gareth Dawson
alertingLokiRangeToInstant,2023-06-16T17:55:49Z,2023-09-13T11:52:40Z,934ba1aaa1fa4c1b9f1dcc0d6753c67a23c4f466,Jean-Philippe Quéméner
lokiExperimentalStreaming,2023-06-19T10:03:51Z,,271cdb4baa6d5b021e013b463350f227173c4e95,Gábor Farkas
flameGraphV2,2023-06-19T14:34:06Z,2023-07-11T12:58:43Z,5ca03a82f042808efabae84b46991106c23de514,Andrej Ocenas
pluginsDynamicAngularDetectionPatterns,2023-06-26T13:33:21Z,2024-04-15T08:37:28Z,cca9d897339ae2de087bc5b7177849a730d4b38d,Giuseppe Guerra
elasticToggleableFilters,2023-06-27T08:38:20Z,2023-07-28T12:49:02Z,c1ce24c90f75daa133e2cb59288ecc14195e5194,Matias Chomicki
vizAndWidgetSplit,2023-06-27T10:22:13Z,,2785ed80d999b9ee1770e1209284d4fccd985401,Alexa V
nestedFolderPicker,2023-06-28T09:40:29Z,2024-06-04T09:16:12Z,f18a7f7d9696a6e80606dc49d5ac0064384f111d,Josh Hunt
frontendSandboxMonitorOnly,2023-07-05T11:48:25Z,,72f6793344fb3a63f5a8a86570b786b518264366,Esteban Beltran
prometheusIncrementalQueryInstrumentation,2023-07-05T19:39:49Z,,daf9f9cd199e0bbc110222a3f005dc05dab1681a,Galen Kistler
dashboardEmbed,2023-07-06T14:43:20Z,2024-04-19T10:48:08Z,420b19e0e4bbdb97ae707cc1360bef7f79839d02,Alex Khomenko
awsDatasourcesTempCredentials,2023-07-06T15:06:11Z,,d33508453f6f1f7aab262b54c36ef471ed3eab87,Ida Štambuk
logsExploreTableVisualisation,2023-07-12T13:52:42Z,,7e4e743a42052183f549f7fa88cd0cc362844ad1,Sven Grossmann
transformationsRedesign,2023-07-12T16:35:49Z,,5099e882276227f4b4c8e695f35156b7873d81b3,Ludovic Viaud
lokiLogsDataplane,2023-07-13T07:58:00Z,,e045860fbf449feb101d187730a46bb1cdab787a,Gábor Farkas
mlExpressions,2023-07-13T17:37:50Z,,541bfe636daa55705a130df4004aa417c99adc09,Yuri Tseretyan
disableTraceQLStreaming,2023-07-14T14:10:46Z,2023-07-26T13:33:16Z,c1709c93013a3fa5797898e81c046a2a53ae7ed4,Andre Pereira
grafanaAPIServer,2023-07-14T19:22:10Z,2024-01-23T16:27:28Z,52121b7165b6ffcdf10dd7a62fdf04043ef0c00a,Todd Treece
featureToggleAdminPage,2023-07-18T20:43:32Z,,600f623610b00547400e00fd0b2ee7dc4bab5849,Ibrahim
lokiFormatQuery,2023-07-21T12:03:56Z,2024-06-05T09:46:28Z,4e42f9b6198d39ffca82c3c3d32a16d54c66090b,Gareth Dawson
splitScopes,2023-07-21T14:23:01Z,2024-02-23T15:53:37Z,cfa1a2c55feea58bd7f82e4e03df2ea712ef34a7,Ieva
awsAsyncQueryCaching,2023-07-21T15:34:07Z,,56913fbd9578c4666599b2ab46423361d6c9261f,Isabella Siu
toggleLabelsInLogsUI,2023-07-24T08:22:47Z,2023-10-27T11:00:49Z,84f94cdc24c3b911ae4f8eabfe875b3e4a9c1f1b,Matias Chomicki
azureMonitorDataplane,2023-07-24T13:50:49Z,2023-11-08T15:02:46Z,ee60d8c82dbcc8496d52d673bfc000901d72f232,Kyle Brandt
gcomOnlyExternalOrgRoleSync,2023-07-25T12:27:02Z,2023-11-13T09:56:02Z,f7c6491f7317f3fbaf4f21b8beec967d76778a87,Ieva
traceQLStreaming,2023-07-26T13:33:16Z,,89092a1e697788428f76fead65371b7c904cd13f,Andre Pereira
prometheusConfigOverhaulAuth,2023-07-26T16:09:53Z,,98cb3ce3b63ee4d1b82e6b04d11f8a6a065de7b4,Brendan O'Handley
configurableSchedulerTick,2023-07-26T16:44:12Z,,c7598cc6fb492cb7cc34eec90fbd2a08d859cffd,Yuri Tseretyan
permissionsFilterRemoveSubquery,2023-08-02T07:39:25Z,,2c26a02b82238e0f89ca08844914cff22a84255a,Sofia Papagiannaki
influxdbSqlSupport,2023-08-02T16:27:43Z,2024-04-18T14:29:27Z,77e7ae2a1b253276ceae4ef5cda1dffc0e674b4d,ismail simsek
noBasicRole,2023-08-04T09:08:14Z,2023-10-12T11:00:26Z,64ed77ddce79009ce05f43801cc578c135f05b6b,linoman
alertingNoDataErrorExecution,2023-08-15T14:27:15Z,,0717ec11d6042bbc5b4cb8d908a396ecec78cdd5,Yuri Tseretyan
metricsSummary,2023-08-28T14:02:12Z,,59e4c257bba00968741ffc235e2dbe9d4c5b90ac,Joey
angularDeprecationUI,2023-08-29T14:05:47Z,,bf4d025012af42ce023b1166e95626f86e819855,Giuseppe Guerra
dashgpt,2023-08-30T20:22:05Z,,2f4fbf89cac96b74c2375b9ec9710a2c890dcf8f,Nathan Marrs
reportingRetries,2023-08-31T07:47:47Z,,f919c55bbb417f3b1023d72bbfce6617acfd1ab9,Agnès Toulet
newBrowseDashboards,2023-09-07T10:41:00Z,2023-11-22T15:22:00Z,ebe13a53f729c265c1f161be4e6f2c29ba074127,Ashley Harrison
sseGroupByDatasource,2023-09-07T20:02:07Z,,5cc737bb24f80514d35b1f02c7113e106a277dc6,Kyle Brandt
requestInstrumentationStatusSource,2023-09-11T10:13:13Z,2024-02-06T08:29:41Z,8ee43f37051e6b6a016e1c38c3d585cc0df47419,Marcus Efraimsson
wargamesTesting,2023-09-13T18:32:01Z,,2d8f5c1488142288d73a4db892e2108b7c7db03b,Carl Bergquist
alertingInsights,2023-09-14T12:58:04Z,,5d88b8a4f58abeafc11553865f99df69b760fcd3,Virginia Cepeda
dockedMegaMenu,2023-09-18T10:57:11Z,2024-02-06T13:43:11Z,0ceeb18269765b40eb5cf7bf2536894152d5e5e8,Laura Fernández
lokiRunQueriesInParallel,2023-09-19T09:34:01Z,,98aa7db64ab4c4d0f3699a9bcbde9894429daa62,Travis Patterson
pluginsAPIMetrics,2023-09-21T11:36:32Z,,8e8bd2760b8c05df9015900ebdf865c1629881de,Esteban Beltran
externalCorePlugins,2023-09-22T08:50:13Z,,61cdfba87a36bd4b8e1bb227a14c5d88fc943aff,Andres Martinez Gotor
httpSLOLevels,2023-09-22T08:52:28Z,2024-02-06T08:29:41Z,e5fbc4a4cd587e4b342afd0ab0136754cf8ebf76,Carl Bergquist
idForwarding,2023-09-25T15:21:28Z,,d15661c726b582212e1329f0ebe938664445e44a,Karl Persson
cloudwatchNewRegionsHandler,2023-09-25T18:19:12Z,2024-01-30T12:11:52Z,ef441f02d09730449f3f7bbdcf3b31dc00ed797a,Sarah Zinger
cloudWatchWildCardDimensionValues,2023-09-27T14:41:48Z,2024-03-15T13:49:53Z,06a35f55ac56ca5699031b2af9202ac7a26918fc,Isabella Siu
externalServiceAccounts,2023-09-28T07:26:37Z,,4563fc48afe81ff1af54b6a5bf6d65355f961155,Gabriel MABILLE
alertingModifiedExport,2023-09-28T14:07:45Z,2023-10-12T17:17:32Z,169c5262a5e7d04234119a3733b00bb787b8bf90,Sonia Aguilar
enableNativeHTTPHistogram,2023-10-03T18:23:55Z,,0fc403d116b9cbd0b93e497cff0ddded55f9fe60,Carl Bergquist
transformationsVariableSupport,2023-10-04T14:28:46Z,,40cdb3033697e8d8e43d0ee0342f0d16fac7d633,Oscar Kilhed
kubernetesPlaylists,2023-10-05T19:00:36Z,,664ebf771e2a1d2f94bc14d3dababb634c4ac40f,Todd Treece
grafanaAPIServerWithExperimentalAPIs,2023-10-06T18:55:22Z,,717a9dd6160e352ff7c184bfcddf2410eed9d908,Ryan McKinley
panelMonitoring,2023-10-09T05:19:08Z,,ef82767dabea7d19cd8efac0c36ed590d4d767f4,Victor Marin
navAdminSubsections,2023-10-10T10:50:44Z,2023-11-17T10:04:34Z,f56cc6fdc01dbf2efb802f22650cb2bef0c40179,Ashley Harrison
recoveryThreshold,2023-10-10T14:51:50Z,,810fbc3327841da6d21f945e75cad9daf040e625,Yuri Tseretyan
libraryPanelRBAC,2023-10-11T23:30:50Z,,a12cb8cbf3a9b33841b2f2cb1522be11de78c86a,kay delaney
awsDatasourcesNewFormStyling,2023-10-12T08:59:10Z,,2771fb940342aa152377b26b9554eb15082f90ac,Ida Štambuk
cachingOptimizeSerializationMemoryUsage,2023-10-12T16:56:49Z,,94ce87571ddfcede0fb7a229a65502b385d5bca3,Michael Mandrus
panelTitleSearchInV1,2023-10-13T12:04:24Z,,bf2f2540da7a4e4b8d80e1fa4ae3d05868cf7b69,Arati R
exploreContentOutline,2023-10-13T16:57:13Z,,4ec54bc2c39ba43843c693fdb2a4529b6a4703f2,Haris Rozajac
formatString,2023-10-13T18:17:12Z,,889576ac1d9278b1c6e3e278e8195968646a2db0,Sol
pluginsInstrumentationStatusSource,2023-10-17T08:27:45Z,2024-02-21T11:57:40Z,f5076d1868caa14ce44a70e812315541b4199d9f,Giuseppe Guerra
teamHttpHeaders,2023-10-17T10:23:54Z,,be5ba6813209b5b24e955e0f761032cb5826b578,Eric Leijonmarck
costManagementUi,2023-10-17T16:15:51Z,2024-01-08T14:25:11Z,de1ed216f4bbf6f341aa22b144fa66d583a63981,Adam Bannach
managedPluginsInstall,2023-10-18T13:17:03Z,,43add83d1a84fe0945860a9cc700df92c7e8341f,Hugo Kiyodi Oshiro
prometheusPromQAIL,2023-10-19T15:45:32Z,,5580d061019bee46ea2e69c94041f3da14585ceb,Brendan O'Handley
cloudWatchBatchQueries,2023-10-20T19:09:41Z,,ecbc52f51529e1f35e26895db1a10f8a1c2f4244,Isabella Siu
alertingContactPointsV2,2023-10-25T13:57:53Z,2023-11-30T12:37:14Z,e12e40fc2493160338237b0b94e72fa530a78ef4,Gilles De Mey
alertmanagerRemoteOnly,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago
alertmanagerRemoteSecondary,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago
alertmanagerRemotePrimary,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago
annotationPermissionUpdate,2023-10-31T13:30:13Z,,c51c51458e4dd103aa0c099aa48b0d41f9375f86,Ieva
kubernetesPlaylistsAPI,2023-10-31T17:26:39Z,2023-11-08T19:14:05Z,dd773e74f120ba908cafd596a6875c2d7fa199bf,Ryan McKinley
traceToProfiles,2023-11-01T10:14:24Z,2024-01-22T14:21:14Z,c39e9a8f527b79881b95f64fd1c413b4bff42983,Joey
extractFieldsNameDeduplication,2023-11-02T15:47:42Z,,0eda368d32dbd7f57635cabd6b7d6870ca7b58ee,Oscar Kilhed
dashboardSceneForViewers,2023-11-02T19:02:25Z,,6e80a3d59b14d9af235431d6acdce95835e2a792,Dominik Prokop
panelFilterVariable,2023-11-03T12:15:54Z,,6bf4d0cbc6756ec8e8fb64b3a9d7b76f4c4194f5,Jacob Zelek
addFieldFromCalculationStatFunctions,2023-11-03T14:39:58Z,,61d63d3034d7eed4837c63e1f5cb2f6b7114758d,Victor Marin
pdfTables,2023-11-06T13:39:22Z,,95b48339f89c7d267bce7d894404d26ccd75d0e3,Agnès Toulet
newVizTooltips,2023-11-06T16:35:59Z,2024-04-03T00:32:01Z,6b4b7127544865b78f712d907e6f1719595f4232,Adela Almasan
ssoSettingsApi,2023-11-08T09:50:01Z,,5285e9503be5702680acb2b52a6bda0632f4603d,Misi
logsInfiniteScrolling,2023-11-09T10:54:03Z,,174c2ab45a2af912519153c5c3e671f04396d7d7,Matias Chomicki
flameGraphItemCollapsing,2023-11-09T14:31:07Z,,494a07b522df4e3ff9512b47767745a94c15f080,Andrej Ocenas
alertingDetailsViewV2,2023-11-09T17:35:03Z,2024-03-14T14:18:01Z,323ee7c38ceb18b8e71c780d797fabac7041a673,Gilles De Mey
alertingSimplifiedRouting,2023-11-10T13:14:39Z,,68e37c3925080cf64a5e7570eabb042f19ca2dbf,Sonia Aguilar
dashboardScene,2023-11-13T08:51:21Z,,4bc322ca1d6ed63d7e79eecb1ed09f3043f9aedb,Torkel Ödegaard
datatrails,2023-11-15T11:28:29Z,2024-04-09T18:15:18Z,1f1d348e1700735683dc9b80fce106b8a5bc4cee,Torkel Ödegaard
pluginsSkipHostEnvVars,2023-11-15T17:09:14Z,,cb0a88a02770eaee2561fd434d8339cab268f02e,Giuseppe Guerra
logRowsPopoverMenu,2023-11-16T09:48:10Z,,9cb303c3f701169e8651267744a4b5fe1695a066,Matias Chomicki
lokiStructuredMetadata,2023-11-16T16:06:14Z,,a01f8c5b42bb7937139b8d52e9723cc37e5f7ae6,Sven Grossmann
tracesEmbeddedFlameGraph,2023-11-23T13:36:53Z,2024-01-22T14:21:14Z,4f46fb412ca4e96b0b0ef3f462aead3fc146389e,Joey
regressionTransformation,2023-11-24T14:49:16Z,,ab982e7bd36b86c94093bddcc31008f5dc49660e,Oscar Kilhed
displayAnonymousStats,2023-11-29T16:58:41Z,2024-02-23T15:53:37Z,59bdff0280d52ca5d8918157d7697b9279b25501,Eric Leijonmarck
influxqlStreamingParser,2023-11-29T17:29:35Z,,5845f140758473ab5ffe789bec4077032fd22839,ismail simsek
kubernetesSnapshots,2023-12-05T22:31:49Z,,439edebcd605a1b63bf3a9b0ab5c2b83341cd5cd,Ryan McKinley
grafanaAPIServerEnsureKubectlAccess,2023-12-06T20:21:21Z,,c4c9bfaf2e7fa12a8e453df0f089c8b4f914a3d3,Dan Cech
unifiedStorage,2023-12-06T20:21:21Z,,c4c9bfaf2e7fa12a8e453df0f089c8b4f914a3d3,Dan Cech
alertStateHistoryAnnotationsFromLoki,2023-12-11T19:17:01Z,2024-01-25T17:56:09Z,4c1bf86ae11696277025296b86e8514386b4bb31,William Wernert
tableSharedCrosshair,2023-12-13T09:33:14Z,,5aff3389f4633d6970eb2b3629ac015e564c626c,Victor Marin
lokiQueryHints,2023-12-18T20:43:16Z,,2165c9b3f000f59c9fbda80d2bfe3bc74cd6d9dc,Sven Grossmann
canvasPanelPanZoom,2024-01-02T19:52:21Z,,2502fe4d19faa993da8fd4d21b9802d59f4b02af,Drew Slobodnjak
alertingPreviewUpgrade,2024-01-05T18:31:05Z,2024-03-14T14:36:35Z,49891d6a72537c869628a13ab75c8d616fbac590,Matthew Jacobson
enablePluginsTracingByDefault,2024-01-10T11:25:54Z,2024-04-11T16:40:47Z,b40d3e748717961ef68457442d71c1284189c577,Giuseppe Guerra
cloudRBACRoles,2024-01-10T13:19:01Z,,5b3cd9f55b292f0b693f3bb5171da7b69dac4a95,Karl Persson
alertingQueryOptimization,2024-01-10T20:52:58Z,,afa33f12b2cf50d2c7e438f498d27b0684797778,Matthew Jacobson
newFolderPicker,2024-01-15T11:43:19Z,,ec53487c995777b314f566f5a1054e3f8e29ec05,Ashley Harrison
kubernetesFeatureToggles,2024-01-18T05:32:44Z,,41e523bde7db5706f339d418c68d019039a8062e,Ryan McKinley
returnToPrevious,2024-01-18T17:12:14Z,2024-05-27T15:47:57Z,5800e40fba2accf96d81328f000e35bbb7c7acf1,Laura Fernández
jitterAlertRulesWithinGroups,2024-01-18T18:48:11Z,,00a260effab802edc8f72df50bfb6447aac343f0,Alexander Weaver
jitterAlertRules,2024-01-18T18:48:11Z,2024-02-09T21:53:58Z,00a260effab802edc8f72df50bfb6447aac343f0,Alexander Weaver
onPremToCloudMigrations,2024-01-22T16:09:08Z,,cf13cb9f70c2230f17450667ce59440304fb023c,Michael Mandrus
alertingSaveStatePeriodic,2024-01-23T16:03:30Z,,aa25776f813926cb4f1947d4ae5a014f4e7728ff,Jean-Philippe Quéméner
promQLScope,2024-01-29T20:22:17Z,,43d0664340f3e3af219d7b5c747f486a073f5ce3,Kyle Brandt
slateAutocomplete,2024-01-31T10:01:20Z,2024-02-01T13:03:11Z,39057552dc2ddfe8ee770c67a71cf55159aca741,Ashley Harrison
nodeGraphDotLayout,2024-01-31T16:26:12Z,,cb945aa5df3453ab2a851ef7ec8bee244e3eeac4,Andrej Ocenas
kubernetesQueryServiceRewrite,2024-01-31T18:36:51Z,2024-04-19T09:26:21Z,e013cd427cb0457177e11f19ebd30bc523b36c76,Ryan McKinley
influxdbRunQueriesInParallel,2024-02-01T10:58:24Z,,536153c3362782ae7f79e06755e8c7e0cd3b3acd,ismail simsek
groupToNestedTableTransformation,2024-02-07T14:28:26Z,,756cd3c28b0648f55a6c0158f5faf63b632ebe70,Kyle Cunningham
newPDFRendering,2024-02-08T12:09:34Z,,28e66b4ad82ecebb551374325f0be4332412341c,Agnès Toulet
autoMigrateGraphPanel,2024-02-08T22:00:48Z,,829672759c12b27f849c92c3a2aee4a6b0037920,Nathan Marrs
dashboardSceneSolo,2024-02-11T08:08:47Z,,fe6d1460b09b403fc74fd20e95f61513eede2555,Torkel Ödegaard
kubernetesAggregator,2024-02-12T20:59:35Z,,d6e6298103d5d6a4efd1c21a3d74f429b506f3a7,Todd Treece
autoMigrateStatPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs
autoMigrateWorldmapPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs
autoMigratePiechartPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs
autoMigrateTablePanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs
groupByVariable,2024-02-14T17:18:04Z,,f016f95298fe490a865612864520f3622f8e804a,Dominik Prokop
alertingUpgradeDryrunOnStart,2024-02-16T16:29:54Z,2024-03-14T14:36:35Z,dfaf6d1e2e13b2bd11dc8f0cd4432bfcad819aa9,Matthew Jacobson
expressionParser,2024-02-17T00:59:11Z,,f23f50f58d7ab5cb1fd88b42b6c58ec09c1a159d,Ryan McKinley
sqlExpressions,2024-02-27T21:16:00Z,,70009201d44c2d0ab39cc77081808a69a6c4fd63,Scott Lepper
aiGeneratedDashboardChanges,2024-03-05T12:01:31Z,,a7c06d26f14b2a9fa8faa929a6c9a0c355018429,Ivan Ortega Alba
scopeFilters,2024-03-05T15:41:19Z,,b3efb4217e48656f24aacdffd5737595d7361afe,Carl Bergquist
betterPageScrolling,2024-03-06T15:06:47Z,,6a4e0c692ab26f4d4cb99ae615013e0b6e23f90b,Josh Hunt
emailVerificationEnforcement,2024-03-11T14:09:44Z,2024-03-22T13:30:58Z,0b55d72fb5698e1ea2cf73eaceae166cd5619daa,Karl Persson
ssoSettingsSAML,2024-03-14T11:04:45Z,,831ee9ee1696c0aa7a6e4ca022ddfc0ab28b86dc,linoman
publicDashboardsScene,2024-03-22T14:48:21Z,,8d4ca72f2a0e66c446d58d8bf13fadbc988fce11,Juan Cabanas
autoMigrateXYChartPanel,2024-03-22T15:44:37Z,,d7fa99e2df8269425eefb6373a665d8ec0b219d9,Leon Sorokin
usePrometheusFrontendPackage,2024-03-23T00:47:53Z,2024-04-15T21:45:23Z,d0845952117152e7707f964cc343790497220990,Brendan O'Handley
oauthRequireSubClaim,2024-03-25T13:22:24Z,,2f3a01f79fda7f6c465dd64adf5cf5c7227f9d19,Karl Persson
authAPIAccessTokenAuth,2024-04-02T15:45:15Z,,5340a6e548b1e5fcd3af66695ca34d7e943fd068,Jo
newDashboardWithFiltersAndGroupBy,2024-04-04T11:25:21Z,,32b6ef9d153dc7def2c17b81b02cfb1c412eb315,Dominik Prokop
prometheusCodeModeMetricNamesSearch,2024-04-04T20:38:23Z,,559fab9dc6a6c882e7d9621ba013c3a53347176a,Nick Richmond
cloudWatchNewLabelParsing,2024-04-05T15:57:56Z,,58f32150c262605d188874b88f44a7dece4b9388,Isabella Siu
exploreMetrics,2024-04-09T18:15:18Z,,66c0fd4dcc3202e11f41b302d27894dc162fb288,Darren Janeczek
accessActionSets,2024-04-12T16:19:25Z,,56f4664875047d6861ea3facbc94cd921e263950,Ieva
disableNumericMetricsSortingInExpressions,2024-04-16T14:52:47Z,,d3fee607e2818747ad02daf6b8c58cc801075076,Nick Richmond
queryServiceRewrite,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley
queryServiceFromUI,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley
queryService,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley
grafanaManagedRecordingRules,2024-04-22T17:53:16Z,,c32953e52cf9fd46a462711bc23fde15a5c3b6bf,Alexander Weaver
logsExploreTableDefaultVisualization,2024-05-02T15:28:15Z,,840aeddbd1957117d9b62074c155e87bb4afd4b4,Galen Kistler
autofixDSUID,2024-05-03T11:32:07Z,,b6f899d953a0924760dc5fd5a3d40669c86d475d,Andres Martinez Gotor
newDashboardSharingComponent,2024-05-03T15:02:18Z,,d1434fad3a68bc1d2b49725be31e45526e6ad349,Juan Cabanas
tlsMemcached,2024-05-09T19:12:08Z,,b009536329d110afd807ef2f27f2b7dcc7d310ba,lean.dev
notificationBanner,2024-05-13T09:32:34Z,,f3953b4955c218cc4678e842faa3d3380fd0f8f7,Alex Khomenko
dualWritePlaylistsMode3,2024-05-14T12:11:56Z,2024-05-31T18:18:09Z,6836bfe1ea1bf62f4eb66dc1328a456183874f81,Arati R
dualWritePlaylistsMode2,2024-05-14T12:11:56Z,2024-05-31T18:18:09Z,6836bfe1ea1bf62f4eb66dc1328a456183874f81,Arati R
dashboardRestore,2024-05-16T17:36:26Z,,42d75ac737d7ac001a6d53376e25512408a01db5,Ezequiel Victorero
datasourceProxyDisableRBAC,2024-05-21T13:05:16Z,,0072e4a92d896df343d9586522d6f7533773da78,Aaron Godin
alertingDisableSendAlertsExternal,2024-05-23T12:29:19Z,,8421919cb552b9e8dbd4ebba20bcc67bbc5e6b4f,Steve Simpson
datasourceQueryTypes,2024-05-23T16:46:28Z,,42b0f802de31f3d42ece68544c193a69ea381bc0,Ryan McKinley
alertingListViewV2,2024-05-24T14:40:49Z,,99b5259cc1efa3f5c9dbb9b0153be14437818f38,Gilles De Mey
preserveDashboardStateWhenNavigating,2024-05-27T12:28:06Z,,6e9543e0addab825f6755d4d02c5a15a1598a483,Dominik Prokop
alertingCentralAlertHistory,2024-05-29T15:01:38Z,,289ce6185574df99acea37b86c2e0866ea5940fd,Sonia Aguilar
pluginProxyPreserveTrailingSlash,2024-06-05T11:36:14Z,,fe3e5917f1bc83ab29b6a57578316e054718aa4a,Marcus Efraimsson
kubernetesDashboards,2024-06-05T14:34:23Z,,41e0430f83bf7db50c4caaa1472afa3bf3d5c2dc,Ryan McKinley
azureMonitorPrometheusExemplars,2024-06-06T16:53:17Z,,c9778c3332aa93e5dd8bc3b894264e1955f0d593,Andreas Christou
1 #name created deleted hash author
2 newNavigation 2022-01-26T17:44:20Z 2022-06-16T09:48:38Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
3 trimDefaults 2022-01-26T17:44:20Z 2023-11-02T15:35:14Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
4 dashboardPreviews 2022-01-26T17:44:20Z 2023-04-13T17:42:24Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
5 live-config 2022-01-26T17:44:20Z 2023-02-03T21:21:48Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
6 accesscontrol 2022-01-26T17:44:20Z 2022-05-16T10:45:41Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
7 tempoServiceGraph 2022-01-26T17:44:20Z 2022-07-19T07:00:58Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
8 httpclientprovider_azure_auth 2022-01-26T17:44:20Z 2022-05-30T15:43:32Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
9 tempoBackendSearch 2022-01-26T17:44:20Z 2022-06-01T17:32:10Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
10 showFeatureFlagsInUI 2022-01-26T17:44:20Z 2023-02-09T00:01:34Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
11 queryOverLive 2022-01-26T17:44:20Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
12 validatedQueries 2022-01-26T17:44:20Z 2022-05-16T21:17:05Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
13 fullRangeLogsVolume 2022-01-26T17:44:20Z 2022-02-15T08:05:03Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
14 prometheus_azure_auth 2022-01-26T17:44:20Z 2022-08-11T14:12:57Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
15 live-service-web-worker 2022-01-26T17:44:20Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
16 envelopeEncryption 2022-01-26T17:44:20Z 2022-05-24T08:34:47Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
17 database_metrics 2022-01-26T17:44:20Z 2023-04-28T13:19:06Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
18 live-pipeline 2022-01-26T17:44:20Z 2023-03-22T18:09:44Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
19 service-accounts 2022-01-26T17:44:20Z 2022-04-21T09:41:37Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
20 tempoSearch 2022-01-26T17:44:20Z 2022-06-01T17:32:10Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
21 disable_http_request_histogram 2022-01-26T17:44:20Z 2022-06-01T12:33:59Z 5d66194ec5d8a7c174a075b6023dcbf58d17b861 Ryan McKinley
22 featureHighlights 2022-02-03T11:53:23Z a79c048344bddff7a868b040d9a08953917480f9 Alex Khomenko
23 lokiBackendMode 2022-02-07T07:43:48Z 2022-06-08T06:14:34Z 560c77390550e12e8c0be507c00f27cde0aa31e5 Gábor Farkas
24 swaggerUi 2022-02-08T12:38:43Z 2023-03-01T14:36:37Z 35fe58de374003bb4b077a878cc47ffc0a9d27b5 Sofia Papagiannaki
25 influxdbBackendMigration 2022-02-09T18:26:16Z 2023-01-17T14:11:26Z 10232c78577520ce7b3fc6bba86d2e48ee5bab88 Joey Tawadrous
26 dashboardPreviewsScheduler 2022-02-10T18:45:00Z 2022-04-24T21:55:10Z 0276b029fc656e61763f6ae045ad7826380a43fc Artur Wierzbicki
27 migrationLocking 2022-02-15T16:54:27Z 2024-03-22T19:22:29Z d718ee191834b3084b2e8c8c6cac473bd9b97681 Sofia Papagiannaki
28 panelTitleSearch 2022-02-15T18:26:03Z d665306ad1c2abe515c7ba48350904f749eafc18 Ryan McKinley
29 dashboardPreviewsAdmin 2022-02-17T08:34:07Z 2023-02-03T21:45:10Z 3e53a08090ad8ac61d7d316b97a93a7a5ea63a57 Artur Wierzbicki
30 annotationComments 2022-02-22T07:47:42Z 2023-03-11T12:28:12Z 28c30a34adbe94d0f08acdba3c1605143dce6c6c Alexander Emelin
31 dashboardComments 2022-02-22T07:47:42Z 2023-03-11T12:28:12Z 28c30a34adbe94d0f08acdba3c1605143dce6c6c Alexander Emelin
32 lokiLive 2022-03-01T22:46:52Z 2023-06-19T10:03:51Z 796bc27f75d52148d5b15cc8c4901276c344df2d Ryan McKinley
33 fileStoreApi 2022-03-03T06:53:26Z 2022-03-11T18:08:19Z a8b90d9a2524765c49923c48a7fcf0025c85b733 Artur Wierzbicki
34 azureMonitorResourcePickerForMetrics 2022-03-14T19:07:45Z 2023-01-30T16:19:03Z 275f33cf37bb4221ef120310a87c17d2e0ff5f35 Sarah Zinger
35 storageLocalUpload 2022-03-17T17:19:23Z 2022-07-18T17:44:42Z 1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f Ryan McKinley
36 storage 2022-03-17T17:19:23Z 1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f Ryan McKinley
37 saveDashboardDrawer 2022-03-30T17:07:41Z 2022-05-02T16:29:22Z edf384c730a448a5e90b21bbbce49bc0e70e8197 Ryan McKinley
38 accesscontrol-builtins 2022-03-31T09:40:57Z 2022-05-19T07:29:36Z 0d87de153a2b8406d03efe0cf43b5a926ce7acab Gabriel MABILLE
39 alertProvisioning 2022-04-01T06:32:00Z 2022-06-05T05:45:36Z b8e277ee4c070b64ba4cf024b18c460626d1a2d3 Alexander Weaver
40 explore2Dashboard 2022-04-07T08:26:01Z 2022-11-07T16:06:40Z ca286a238d1e9ce1e5357fde8148476a9d618cee Giordano Ricci
41 publicDashboards 2022-04-07T18:30:19Z 4a4d87dbdcaf4dca753bcf29205ac036b6c59862 Jeff Levin
42 persistNotifications 2022-04-20T09:42:32Z 2022-05-26T11:03:04Z c48d8d1d488e0ce5870c54eb5828660081743108 kay delaney
43 serviceAccounts 2022-04-21T09:41:37Z 2022-06-27T08:22:49Z 8677552dda7b63f5d44df6fe51ca8a28726c5a5c Eric Leijonmarck
44 commandPalette 2022-04-21T21:50:34Z 2023-02-02T14:44:21Z 3e7db088acd1da32ea489b0756cdf4e15ec2da69 Kristina
45 export 2022-04-25T23:59:18Z 2023-03-01T17:42:53Z e0aeb83786731769e870d79c0d8a21ef2c33b073 Ryan McKinley
46 savedItems 2022-04-26T10:27:01Z 2022-06-27T14:41:00Z e420252d45cd48fdcf5cc68ad84f89e36d5c936c Ashley Harrison
47 tracing 2022-04-26T13:31:27Z 2023-03-21T09:35:21Z 3b4d237ade03e67a971ca8899e8e0371e1d83f2a kay delaney
48 cloudWatchDynamicLabels 2022-04-29T09:43:04Z 2023-05-02T08:48:17Z 7bb4f5cd9baff81b354e4bb65fd4b6141e3488f6 Shirley
49 datasourceQueryMultiStatus 2022-05-03T16:02:20Z 4ecd57f49c75d72927be62aba23a15dca02f8eb6 Will Browne
50 azureMonitorExperimentalUI 2022-05-04T13:54:09Z 2022-07-14T13:07:31Z b2644de6c8a08181cbe0a9f545393bd42753477c Adam Simpson
51 traceToMetrics 2022-05-05T20:46:18Z 2024-02-22T13:30:41Z c1b5ea3e54169c7a4bbd58bd8be2c4c192a093c0 Connor Lindsey
52 prometheusStreamingJSONParser 2022-05-13T18:28:54Z 2022-11-11T16:53:12Z 87e8521591534bcfd8a7c78fbd39b226c13a99b2 Todd Treece
53 validateDashboardsOnSave 2022-05-22T00:44:12Z 2023-09-06T15:25:44Z a3402641d6cac01208179126dcbd2256ad500cd8 sam boyer
54 disableEnvelopeEncryption 2022-05-24T08:34:47Z 3e4b4dba46e5310015c7de5c3de1f3fae5332cb8 Joan López de la Franca Beltran
55 prometheusWideSeries 2022-05-24T20:17:11Z 2023-08-24T14:47:19Z 94b9c524a81df2085ad769229ca09cfd1923a8e9 Todd Treece
56 prometheusAzureOverrideAudience 2022-05-30T15:43:32Z 2023-07-16T21:30:14Z 2b83cf46186f745e356a24e4c78a1fbc24b3051e Sergey Kostrukov
57 canvasPanelNesting 2022-05-31T19:03:34Z e18e8002c4dda17e95cb4f04977a6aefcc5984a4 Adela Almasan
58 cloudMonitoringExperimentalUI 2022-06-09T13:56:32Z 2022-09-15T14:12:26Z 615132002044e56074be63c5b597b9982e0a2a21 Kevin Yu
59 tempoApmTable 2022-06-09T16:56:15Z 2023-02-01T15:56:34Z 4ed7ff2ed13f2b5ec79f0946670c945edc902d7b Joey Tawadrous
60 logRequestsInstrumentedAsUnknown 2022-06-10T08:56:55Z 2d6e69226be20a7f001c7792bda8df68d90287c6 Carl Bergquist
61 dataConnectionsConsole 2022-06-10T10:13:31Z 2024-02-29T09:29:41Z 9a85a2e4410266270a1cd59db1937c7ba248966e Levente Balogh
62 internationalization 2022-06-10T11:02:52Z 2023-06-02T08:24:40Z 9a62849dc39e960640b5762cc5eace1baf29c0ee Josh Hunt
63 autoMigrateGraphPanels 2022-06-11T00:12:56Z 2023-03-23T04:02:36Z ae8dd737704bb6232c52e19090e593d9165f5e77 Ryan McKinley
64 lokiDataframeApi 2022-06-13T06:33:46Z 2023-04-13T13:22:09Z 8fd9cb48548313cf43ebcf5dcfa0daad0bfdfe0c Gábor Farkas
65 topnav 2022-06-20T14:25:43Z 3c3293df78344a782fb65e5f977fd148daa597ce Torkel Ödegaard
66 customBranding 2022-06-22T15:05:52Z 2022-10-05T12:07:35Z 405df77e3e6abcf5264ba192abe33630bd64da20 Tania
67 useLegacyHeatmapPanel 2022-06-23T18:48:28Z 2022-11-23T18:46:21Z dd5a3b77472884035de13ddd441f41d0dd007e4b Ryan McKinley
68 scenes 2022-07-07T06:53:02Z 935334cbdabef8b0516bfe6c8ed45dd063cce7e9 Torkel Ödegaard
69 disableSecretsCompatibility 2022-07-12T20:27:37Z 2d8a91a8461098f83aa512c867aff5af17b7893e Guilherme Caulada
70 dashboardsFromStorage 2022-07-14T22:36:17Z 2023-03-20T16:36:49Z da1701ce576ab26506d6256567b73a597043623a Ryan McKinley
71 exploreMixedDatasource 2022-07-27T14:40:59Z 2022-07-27T15:17:31Z e2258120e742b31ecde50e8de93544220a0762a3 Kristina
72 prometheusStreamingJSONParserTest 2022-07-28T12:26:51Z 2022-11-07T18:20:00Z d0e548c3e53ffdbc29f35e055d52aa63cca310db Andrej Ocenas
73 fullstoryUserTracking 2022-08-16T08:32:00Z 2022-08-16T10:45:14Z 961479b1115d39f3918b60c49a48841916939f03 Ivan Ortega Alba
74 traceqlEditor 2022-08-24T16:57:59Z 2022-12-13T13:27:45Z c8f2148f7504b393b8f9b32fe19e7f519fa66793 Andre Pereira
75 athenaAsyncQueryDataSupport 2022-09-05T15:39:45Z 2024-03-20T14:14:21Z 34fe7a1119c0f649d3e9df866353438ff2177fdc Kevin Yu
76 redshiftAsyncQueryDataSupport 2022-09-05T15:39:45Z 2024-03-20T14:14:21Z 34fe7a1119c0f649d3e9df866353438ff2177fdc Kevin Yu
77 increaseInMemDatabaseQueryCache 2022-09-12T07:50:54Z 2023-02-06T21:01:04Z 72ae4a5aa368d18793f778683e92ae4a3ac7a1db Carl Bergquist
78 correlations 2022-09-16T13:14:27Z 9b4cdfe6526a290e5708a8c0760d50efda607d5c Piotr Jamróz
79 grpcServer 2022-09-26T20:25:34Z 55aae797441d2beadc911e390f14c04bb3f101da Alexander Emelin
80 alertingBigTransactions 2022-10-06T06:22:58Z 2023-04-06T16:06:25Z b476ae62fba9b94dd272409c521812108750bf50 Joe Blubaugh
81 lokiMonacoEditor 2022-10-06T14:35:30Z 2023-02-06T10:18:01Z 729ce8bb72a5db53fbeb1b5856557652bb175c7a Matias Chomicki
82 objectStore 2022-10-06T19:48:53Z 2022-11-30T22:52:15Z 609abf00d1c6bd065f163e06882aa2f09dc0fcb5 Ryan McKinley
83 flameGraph 2022-10-07T10:39:14Z 2023-03-01T11:32:39Z 74c809f54459d235f4d3e9b1c117f291cbf16339 Joey Tawadrous
84 queryLibrary 2022-10-07T18:31:45Z 2023-03-20T16:00:14Z bf264d2f76a1efb902b41791e110077d5c3a5557 Artur Wierzbicki
85 newPanelChromeUI 2022-10-10T15:03:16Z 2023-09-12T08:54:41Z 129a5a29e78853bb1427c8dc7473125ba2a35981 Polina Boneva
86 mysqlAnsiQuotes 2022-10-12T11:43:35Z 80ede174dd6e343722cc708ebc4c5f21fb9e90af ying-jeanne
87 showDashboardValidationWarnings 2022-10-14T13:51:05Z 2e16d5499e1cf29e67db5031feda09407f69550c Josh Hunt
88 interFont 2022-10-15T14:22:33Z 2022-12-01T11:59:37Z 9f5e691994c9db15f5984b196ab55fd7213b7f72 Torkel Ödegaard
89 accessControlOnCall 2022-10-19T16:10:09Z 717bd4a6c051d1447c9e35385a4ba176dd391c65 Gabriel MABILLE
90 newDBLibrary 2022-10-26T01:20:41Z 2023-11-14T14:51:35Z a3acfb1a48126119fd4913efbb572de162264e92 Ryan McKinley
91 nestedFolders 2022-10-26T14:15:14Z b346ae03105af606521bbefec16722d590378646 Kristin Laemmert
92 datasourceLogger 2022-11-02T13:51:51Z 2023-02-07T11:49:16Z 06705a49e236dc3ab3b35db51b7bad8625e4866f Carl Bergquist
93 promQueryBuilder 2022-11-03T17:34:01Z 2022-12-19T13:52:06Z 857e545c5ac71722c8b2d3d27621dfb453dbb8ff Ryan McKinley
94 elasticsearchBackendMigration 2022-11-10T15:35:15Z 2023-04-12T12:20:43Z 261d620f1c46eb43282cc444ffb4633b77af4283 Ivana Huckova
95 prometheusBufferedClient 2022-11-11T16:53:12Z 2022-12-12T17:05:55Z 1c5039085bdf9252d0aa1666675f5313589c1703 ismail simsek
96 accessTokenExpirationCheck 2022-11-14T15:47:46Z 2023-12-15T12:20:17Z 4915d21c25891dae2ee7a4eec4fe0dd2659d614d Misi
97 cloudWatchCrossAccountQuerying 2022-11-28T11:39:12Z 254577ba568d8010dcc96446e788866d618c96c8 Erik Sundell
98 authnService 2022-11-29T09:57:47Z 2023-06-07T06:57:41Z 17ec4089dc75da0e54edf60a754a0fc9291275e3 Karl Persson
99 secureSocksDatasourceProxy 2022-11-30T05:50:59Z 2023-04-18T17:11:23Z 6805c951e94c626401770ed468c6afb462093c0d Stephanie Hingtgen
100 entityStore 2022-11-30T22:52:15Z 2023-12-06T20:21:21Z 14a080ec122f8ff3f656f44a46683f5161c15e14 Ryan McKinley
101 sessionRemoteCache 2022-12-07T09:55:48Z 2023-02-06T12:51:12Z df4f0343e587695f37fa7a6de04cc884af4df284 Jo
102 returnUnameHeader 2022-12-07T12:14:53Z 2023-02-07T09:04:20Z 1b676d0d49460572352af4639274a4e7fe03fc9b Carl Bergquist
103 datasourceOnboarding 2022-12-07T23:29:38Z 2023-05-17T12:22:40Z 955bf55c6a6a710a2fd7ebdbdac2d9a826858eaf kay delaney
104 k8s 2022-12-13T15:41:16Z 2023-04-26T13:52:13Z cf055ab4ecda9db9427ae09560aa26553433a5c0 Ryan McKinley
105 alertingBacktesting 2022-12-14T14:44:14Z ad09feed837737c188e37fc6adfead7b8a45715e Yuri Tseretyan
106 disablePrometheusExemplarSampling 2022-12-19T15:00:15Z 2024-01-30T16:00:04Z f67b8fe0dc2a2d860e71887a4d2e6e25da80bd8f Ludovic Viaud
107 supportBundles 2022-12-20T10:13:37Z 2023-02-10T09:12:04Z 2c7410c87d81b8a644e738fee2bb6f34355111c6 Jo
108 publicDashboardsEmailSharing 2023-01-03T19:45:15Z f0ee3ac80ae2af695c56e709af435c155a2fb33d owensmallwood
109 alertingNoNormalState 2023-01-13T23:29:29Z 9d57b1c72eb817c5fe1b43ad7bc9706a264b2da0 Yuri Tseretyan
110 azureMultipleResourcePicker 2023-01-20T15:29:23Z 2023-01-30T16:19:03Z b1efd911c1f7bfabe1b8231b059470c72dce4712 Andres Martinez Gotor
111 editPanelCSVDragAndDrop 2023-01-24T09:43:44Z 4167214e3535f41c85004c6aca86fcb9ae8956dd Zoltán Bedi
112 topNavCommandPalette 2023-01-24T12:41:09Z 2023-02-24T12:14:53Z 88347caf5fcc99c21c8a07367b9e846947367636 Josh Hunt
113 k8sDashboards 2023-01-25T19:10:16Z 2023-02-09T17:54:00Z 4965cf2edae6eae8e341e6c93c3a1aa56417efb3 Ryan McKinley
114 logsContextDatasourceUi 2023-01-27T14:12:01Z 7c02d9bb8a864aab35c676f1a44c6c5be9eb6c09 Sven Grossmann
115 logsSampleInExplore 2023-01-27T16:30:25Z 2023-07-24T12:24:43Z d5294eb8fa71ba62546d00ebce7b9376ab6f2b1a Ivana Huckova
116 apiserver 2023-02-01T20:28:19Z 2023-02-09T17:54:00Z d4f4a83574dd03a5419fa890b916d09d03cb9acd Ryan McKinley
117 lokiQuerySplitting 2023-02-09T17:27:02Z 94241f6676cf36824a180c11c2824ad41c81a470 Matias Chomicki
118 individualCookiePreferences 2023-02-21T10:19:07Z 0caacb3333152015bf0369f54315a26ff6f60f88 Emil Tullstedt
119 newTraceView 2023-02-28T15:41:40Z 2023-04-21T10:31:24Z 82bcfb492867138bcc4744f88d36eb081c1b6811 Joey
120 drawerDataSourcePicker 2023-03-01T10:26:19Z 2023-04-14T11:01:10Z dc1600ff14acc3deecc5de02f6584dcf20d3c8b1 Oscar Kilhed
121 traceqlSearch 2023-03-06T16:31:08Z 2023-07-24T15:26:10Z fd37ff29b57520036fdc02f8a93a7d74770fd153 Andre Pereira
122 prometheusMetricEncyclopedia 2023-03-07T18:41:05Z 9b6e531549e145d1a1a0ede870a6163c69b0bfd5 Brendan O'Handley
123 timeSeriesTable 2023-03-10T12:41:06Z 2023-10-13T08:00:42Z 548a5054ad8fa5c199bd01e20a02fbbe558d52fe Domas
124 lokiQuerySplittingConfig 2023-03-20T15:51:36Z 68551ac9ca5b92621e6ab895c759822797bbf418 Sven Grossmann
125 onlyExternalOrgRoleSync 2023-03-22T17:41:59Z 2023-07-25T09:51:47Z 3cd952b8bad8d23daee10fcc303a6d19c8b6d0a0 Eric Leijonmarck
126 autoMigrateOldPanels 2023-03-23T04:02:36Z baf5a1d1411ee7e5f297aba099e735ebeee3ab83 Ryan McKinley
127 clientTokenRotation 2023-03-23T13:39:04Z 2024-02-16T14:03:37Z 382b24742ab8b124af072533f74027a1a103b037 Karl Persson
128 disableAngular 2023-03-23T15:43:45Z 58704390269c8eeedd32c571245d5ef08f0ffb82 Ryan McKinley
129 disableElasticsearchBackendExploreQuery 2023-03-27T13:52:27Z 2023-04-12T12:20:43Z f3da91f53fa32a8e870124e9514a010b97359529 Ivana Huckova
130 emptyDashboardPage 2023-03-28T09:42:23Z 2024-01-25T14:04:29Z 221c5efedc593fb42d87c849b3d0827e325fc6cd Polina Boneva
131 prometheusDataplane 2023-03-29T15:26:32Z 674144c8e84cb4aca3bfc448f08b88bb516d3b77 Kyle Brandt
132 alertStateHistoryLokiOnly 2023-03-30T18:53:21Z b2abb6328677a90e3f3bc8b706d22734696d02e3 Alexander Weaver
133 alertStateHistoryLokiPrimary 2023-03-30T18:53:21Z b2abb6328677a90e3f3bc8b706d22734696d02e3 Alexander Weaver
134 alertStateHistoryLokiSecondary 2023-03-30T18:53:21Z b2abb6328677a90e3f3bc8b706d22734696d02e3 Alexander Weaver
135 unifiedRequestLog 2023-03-31T13:38:09Z be9361cb9ec2f1ad0bc0cda139c56e3cd46deb16 Emil Tullstedt
136 prometheusResourceBrowserCache 2023-04-03T14:07:17Z 2023-10-11T13:18:56Z 96e9e807396b600c3325f4d3580eeef0710f6050 Galen Kistler
137 renderAuthJWT 2023-04-03T16:53:38Z 87a0c95164074dce6a877ea3faa3c7ac0e705ad3 Joan López de la Franca Beltran
138 showTraceId 2023-04-05T07:13:24Z 2023-07-20T05:59:38Z 4ded937c79a38087576b3224bf6f2ba59345571e Gábor Farkas
139 pyroscopeFlameGraph 2023-04-05T12:57:24Z 2023-08-02T15:01:12Z 37eaf5019751aa76568574757830d63b764bbb1f Andrej Ocenas
140 dataplaneFrontendFallback 2023-04-07T21:13:19Z af31c77331c89c6a1f8e00747c44c3f39bb737bb Ryan McKinley
141 externalServiceAuth 2023-04-11T13:21:19Z 2024-02-26T10:29:09Z 5df9e64986e8c0324b4da96de5b55e533c3990ed Gabriel MABILLE
142 disableElasticsearchBackendQuerying 2023-04-12T12:20:43Z 2023-04-14T09:24:35Z 303777e682758076d6a8e1df6b82f97b3b2393a7 Ivana Huckova
143 disableSSEDataplane 2023-04-12T16:24:34Z e78be44e1a38b7afb85909b807b6a2e9e5a98830 Kyle Brandt
144 useCachingService 2023-04-12T16:30:33Z 2024-01-17T21:53:25Z 5626461b3c358f5c5c5c690b82977c09744d09b4 Michael Mandrus
145 lokiMetricDataplane 2023-04-13T13:07:08Z 531caec60263738ed6b31878b1ec093de635e4e2 Gábor Farkas
146 authenticationConfigUI 2023-04-13T14:07:43Z 2023-06-14T16:58:15Z 7476219b0c2876f60bc9c6e28b22190f239e1426 Alexander Zobnin
147 enableElasticsearchBackendQuerying 2023-04-14T09:24:35Z 2024-06-05T15:03:29Z f48c858ca22b3f246d1033670f14605adb13cdf3 Ivana Huckova
148 advancedDataSourcePicker 2023-04-14T11:01:10Z 2024-02-06T09:20:07Z 7bc31ab04b5c87f06bdb383c7f4a6195e70d53d1 Ivan Ortega Alba
149 opensearchDetectVersion 2023-04-14T17:59:31Z 2023-05-16T14:22:23Z 1b0cee491a06fa135936b883406a6b11e6221322 Isabella Siu
150 pluginsAPIManifestKey 2023-04-18T14:12:05Z 2023-06-16T09:20:30Z 98c695c68fb3f0d276da0d98b1e78186e459db50 Andres Martinez Gotor
151 newTraceViewHeader 2023-04-21T10:31:24Z 2023-07-19T13:31:58Z 6522bb377e1bc4b64fe0aebd7e34ec2111859e11 Joey
152 enableDatagridEditing 2023-04-24T14:46:31Z efd0e9cbea4a6c4c51337f14004f2df2f876fdb1 Victor Marin
153 faroDatasourceSelector 2023-05-05T00:35:10Z e7cbe0276e3a6f41c31dd8e1ac16ccaa90b184f3 Elliot Kirk
154 extraThemes 2023-05-10T13:37:04Z f8cf67347f069310b8cc6a2d9ff83345a81e3247 Torkel Ödegaard
155 dataSourcePageHeader 2023-05-23T13:18:00Z 2023-11-06T20:21:15Z 7f84e83ffee53446b4c458c29f7c1385dac09ce3 Taewoo K
156 alertingNotificationsPoliciesMatchingInstances 2023-05-30T13:15:22Z 2023-11-09T17:35:03Z 2f0728ac677c26b291947354058253d2e8a50bb7 Konrad Lalik
157 lokiPredefinedOperations 2023-06-02T10:52:36Z 06003c98c855fcb7c1c718c3f0e5f190b644656a Ivana Huckova
158 pluginsFrontendSandbox 2023-06-05T08:51:36Z 1ed4c0382b3812eecf9722596d8a3ac922f3543d Esteban Beltran
159 refactorVariablesTimeRange 2023-06-06T13:12:09Z 07dd90b5a8866273813c6b4a5e7087cea529f908 Alexa V
160 sqlDatasourceDatabaseSelection 2023-06-06T16:28:52Z c0a1fc2cbdc1ceab4fdcb43270fd3720efe4494a Jev Forsberg
161 cloudWatchLogsMonacoEditor 2023-06-12T13:49:52Z 2024-03-18T12:56:57Z 5a831d877ac3305e7dfdb54057069b41b36b40fa Isabella Siu
162 recordedQueriesMulti 2023-06-14T12:34:22Z db75f20e53e717503fe3bf27217fedc40f4a12e1 Kyle Brandt
163 exploreScrollableLogsContainer 2023-06-15T11:25:34Z 2024-03-12T14:53:13Z cda10fae525d5cf784368791e1aaa1de2ce88f4b Gareth Dawson
164 alertingLokiRangeToInstant 2023-06-16T17:55:49Z 2023-09-13T11:52:40Z 934ba1aaa1fa4c1b9f1dcc0d6753c67a23c4f466 Jean-Philippe Quéméner
165 lokiExperimentalStreaming 2023-06-19T10:03:51Z 271cdb4baa6d5b021e013b463350f227173c4e95 Gábor Farkas
166 flameGraphV2 2023-06-19T14:34:06Z 2023-07-11T12:58:43Z 5ca03a82f042808efabae84b46991106c23de514 Andrej Ocenas
167 pluginsDynamicAngularDetectionPatterns 2023-06-26T13:33:21Z 2024-04-15T08:37:28Z cca9d897339ae2de087bc5b7177849a730d4b38d Giuseppe Guerra
168 elasticToggleableFilters 2023-06-27T08:38:20Z 2023-07-28T12:49:02Z c1ce24c90f75daa133e2cb59288ecc14195e5194 Matias Chomicki
169 vizAndWidgetSplit 2023-06-27T10:22:13Z 2785ed80d999b9ee1770e1209284d4fccd985401 Alexa V
170 nestedFolderPicker 2023-06-28T09:40:29Z 2024-06-04T09:16:12Z f18a7f7d9696a6e80606dc49d5ac0064384f111d Josh Hunt
171 frontendSandboxMonitorOnly 2023-07-05T11:48:25Z 72f6793344fb3a63f5a8a86570b786b518264366 Esteban Beltran
172 prometheusIncrementalQueryInstrumentation 2023-07-05T19:39:49Z daf9f9cd199e0bbc110222a3f005dc05dab1681a Galen Kistler
173 dashboardEmbed 2023-07-06T14:43:20Z 2024-04-19T10:48:08Z 420b19e0e4bbdb97ae707cc1360bef7f79839d02 Alex Khomenko
174 awsDatasourcesTempCredentials 2023-07-06T15:06:11Z d33508453f6f1f7aab262b54c36ef471ed3eab87 Ida Štambuk
175 logsExploreTableVisualisation 2023-07-12T13:52:42Z 7e4e743a42052183f549f7fa88cd0cc362844ad1 Sven Grossmann
176 transformationsRedesign 2023-07-12T16:35:49Z 5099e882276227f4b4c8e695f35156b7873d81b3 Ludovic Viaud
177 lokiLogsDataplane 2023-07-13T07:58:00Z e045860fbf449feb101d187730a46bb1cdab787a Gábor Farkas
178 mlExpressions 2023-07-13T17:37:50Z 541bfe636daa55705a130df4004aa417c99adc09 Yuri Tseretyan
179 disableTraceQLStreaming 2023-07-14T14:10:46Z 2023-07-26T13:33:16Z c1709c93013a3fa5797898e81c046a2a53ae7ed4 Andre Pereira
180 grafanaAPIServer 2023-07-14T19:22:10Z 2024-01-23T16:27:28Z 52121b7165b6ffcdf10dd7a62fdf04043ef0c00a Todd Treece
181 featureToggleAdminPage 2023-07-18T20:43:32Z 600f623610b00547400e00fd0b2ee7dc4bab5849 Ibrahim
182 lokiFormatQuery 2023-07-21T12:03:56Z 2024-06-05T09:46:28Z 4e42f9b6198d39ffca82c3c3d32a16d54c66090b Gareth Dawson
183 splitScopes 2023-07-21T14:23:01Z 2024-02-23T15:53:37Z cfa1a2c55feea58bd7f82e4e03df2ea712ef34a7 Ieva
184 awsAsyncQueryCaching 2023-07-21T15:34:07Z 56913fbd9578c4666599b2ab46423361d6c9261f Isabella Siu
185 toggleLabelsInLogsUI 2023-07-24T08:22:47Z 2023-10-27T11:00:49Z 84f94cdc24c3b911ae4f8eabfe875b3e4a9c1f1b Matias Chomicki
186 azureMonitorDataplane 2023-07-24T13:50:49Z 2023-11-08T15:02:46Z ee60d8c82dbcc8496d52d673bfc000901d72f232 Kyle Brandt
187 gcomOnlyExternalOrgRoleSync 2023-07-25T12:27:02Z 2023-11-13T09:56:02Z f7c6491f7317f3fbaf4f21b8beec967d76778a87 Ieva
188 traceQLStreaming 2023-07-26T13:33:16Z 89092a1e697788428f76fead65371b7c904cd13f Andre Pereira
189 prometheusConfigOverhaulAuth 2023-07-26T16:09:53Z 98cb3ce3b63ee4d1b82e6b04d11f8a6a065de7b4 Brendan O'Handley
190 configurableSchedulerTick 2023-07-26T16:44:12Z c7598cc6fb492cb7cc34eec90fbd2a08d859cffd Yuri Tseretyan
191 permissionsFilterRemoveSubquery 2023-08-02T07:39:25Z 2c26a02b82238e0f89ca08844914cff22a84255a Sofia Papagiannaki
192 influxdbSqlSupport 2023-08-02T16:27:43Z 2024-04-18T14:29:27Z 77e7ae2a1b253276ceae4ef5cda1dffc0e674b4d ismail simsek
193 noBasicRole 2023-08-04T09:08:14Z 2023-10-12T11:00:26Z 64ed77ddce79009ce05f43801cc578c135f05b6b linoman
194 alertingNoDataErrorExecution 2023-08-15T14:27:15Z 0717ec11d6042bbc5b4cb8d908a396ecec78cdd5 Yuri Tseretyan
195 metricsSummary 2023-08-28T14:02:12Z 59e4c257bba00968741ffc235e2dbe9d4c5b90ac Joey
196 angularDeprecationUI 2023-08-29T14:05:47Z bf4d025012af42ce023b1166e95626f86e819855 Giuseppe Guerra
197 dashgpt 2023-08-30T20:22:05Z 2f4fbf89cac96b74c2375b9ec9710a2c890dcf8f Nathan Marrs
198 reportingRetries 2023-08-31T07:47:47Z f919c55bbb417f3b1023d72bbfce6617acfd1ab9 Agnès Toulet
199 newBrowseDashboards 2023-09-07T10:41:00Z 2023-11-22T15:22:00Z ebe13a53f729c265c1f161be4e6f2c29ba074127 Ashley Harrison
200 sseGroupByDatasource 2023-09-07T20:02:07Z 5cc737bb24f80514d35b1f02c7113e106a277dc6 Kyle Brandt
201 requestInstrumentationStatusSource 2023-09-11T10:13:13Z 2024-02-06T08:29:41Z 8ee43f37051e6b6a016e1c38c3d585cc0df47419 Marcus Efraimsson
202 wargamesTesting 2023-09-13T18:32:01Z 2d8f5c1488142288d73a4db892e2108b7c7db03b Carl Bergquist
203 alertingInsights 2023-09-14T12:58:04Z 5d88b8a4f58abeafc11553865f99df69b760fcd3 Virginia Cepeda
204 dockedMegaMenu 2023-09-18T10:57:11Z 2024-02-06T13:43:11Z 0ceeb18269765b40eb5cf7bf2536894152d5e5e8 Laura Fernández
205 lokiRunQueriesInParallel 2023-09-19T09:34:01Z 98aa7db64ab4c4d0f3699a9bcbde9894429daa62 Travis Patterson
206 pluginsAPIMetrics 2023-09-21T11:36:32Z 8e8bd2760b8c05df9015900ebdf865c1629881de Esteban Beltran
207 externalCorePlugins 2023-09-22T08:50:13Z 61cdfba87a36bd4b8e1bb227a14c5d88fc943aff Andres Martinez Gotor
208 httpSLOLevels 2023-09-22T08:52:28Z 2024-02-06T08:29:41Z e5fbc4a4cd587e4b342afd0ab0136754cf8ebf76 Carl Bergquist
209 idForwarding 2023-09-25T15:21:28Z d15661c726b582212e1329f0ebe938664445e44a Karl Persson
210 cloudwatchNewRegionsHandler 2023-09-25T18:19:12Z 2024-01-30T12:11:52Z ef441f02d09730449f3f7bbdcf3b31dc00ed797a Sarah Zinger
211 cloudWatchWildCardDimensionValues 2023-09-27T14:41:48Z 2024-03-15T13:49:53Z 06a35f55ac56ca5699031b2af9202ac7a26918fc Isabella Siu
212 externalServiceAccounts 2023-09-28T07:26:37Z 4563fc48afe81ff1af54b6a5bf6d65355f961155 Gabriel MABILLE
213 alertingModifiedExport 2023-09-28T14:07:45Z 2023-10-12T17:17:32Z 169c5262a5e7d04234119a3733b00bb787b8bf90 Sonia Aguilar
214 enableNativeHTTPHistogram 2023-10-03T18:23:55Z 0fc403d116b9cbd0b93e497cff0ddded55f9fe60 Carl Bergquist
215 transformationsVariableSupport 2023-10-04T14:28:46Z 40cdb3033697e8d8e43d0ee0342f0d16fac7d633 Oscar Kilhed
216 kubernetesPlaylists 2023-10-05T19:00:36Z 664ebf771e2a1d2f94bc14d3dababb634c4ac40f Todd Treece
217 grafanaAPIServerWithExperimentalAPIs 2023-10-06T18:55:22Z 717a9dd6160e352ff7c184bfcddf2410eed9d908 Ryan McKinley
218 panelMonitoring 2023-10-09T05:19:08Z ef82767dabea7d19cd8efac0c36ed590d4d767f4 Victor Marin
219 navAdminSubsections 2023-10-10T10:50:44Z 2023-11-17T10:04:34Z f56cc6fdc01dbf2efb802f22650cb2bef0c40179 Ashley Harrison
220 recoveryThreshold 2023-10-10T14:51:50Z 810fbc3327841da6d21f945e75cad9daf040e625 Yuri Tseretyan
221 libraryPanelRBAC 2023-10-11T23:30:50Z a12cb8cbf3a9b33841b2f2cb1522be11de78c86a kay delaney
222 awsDatasourcesNewFormStyling 2023-10-12T08:59:10Z 2771fb940342aa152377b26b9554eb15082f90ac Ida Štambuk
223 cachingOptimizeSerializationMemoryUsage 2023-10-12T16:56:49Z 94ce87571ddfcede0fb7a229a65502b385d5bca3 Michael Mandrus
224 panelTitleSearchInV1 2023-10-13T12:04:24Z bf2f2540da7a4e4b8d80e1fa4ae3d05868cf7b69 Arati R
225 exploreContentOutline 2023-10-13T16:57:13Z 4ec54bc2c39ba43843c693fdb2a4529b6a4703f2 Haris Rozajac
226 formatString 2023-10-13T18:17:12Z 889576ac1d9278b1c6e3e278e8195968646a2db0 Sol
227 pluginsInstrumentationStatusSource 2023-10-17T08:27:45Z 2024-02-21T11:57:40Z f5076d1868caa14ce44a70e812315541b4199d9f Giuseppe Guerra
228 teamHttpHeaders 2023-10-17T10:23:54Z be5ba6813209b5b24e955e0f761032cb5826b578 Eric Leijonmarck
229 costManagementUi 2023-10-17T16:15:51Z 2024-01-08T14:25:11Z de1ed216f4bbf6f341aa22b144fa66d583a63981 Adam Bannach
230 managedPluginsInstall 2023-10-18T13:17:03Z 43add83d1a84fe0945860a9cc700df92c7e8341f Hugo Kiyodi Oshiro
231 prometheusPromQAIL 2023-10-19T15:45:32Z 5580d061019bee46ea2e69c94041f3da14585ceb Brendan O'Handley
232 cloudWatchBatchQueries 2023-10-20T19:09:41Z ecbc52f51529e1f35e26895db1a10f8a1c2f4244 Isabella Siu
233 alertingContactPointsV2 2023-10-25T13:57:53Z 2023-11-30T12:37:14Z e12e40fc2493160338237b0b94e72fa530a78ef4 Gilles De Mey
234 alertmanagerRemoteOnly 2023-10-30T16:27:08Z 363830883cb1f5de30f7015df5cba419df47468e Santiago
235 alertmanagerRemoteSecondary 2023-10-30T16:27:08Z 363830883cb1f5de30f7015df5cba419df47468e Santiago
236 alertmanagerRemotePrimary 2023-10-30T16:27:08Z 363830883cb1f5de30f7015df5cba419df47468e Santiago
237 annotationPermissionUpdate 2023-10-31T13:30:13Z c51c51458e4dd103aa0c099aa48b0d41f9375f86 Ieva
238 kubernetesPlaylistsAPI 2023-10-31T17:26:39Z 2023-11-08T19:14:05Z dd773e74f120ba908cafd596a6875c2d7fa199bf Ryan McKinley
239 traceToProfiles 2023-11-01T10:14:24Z 2024-01-22T14:21:14Z c39e9a8f527b79881b95f64fd1c413b4bff42983 Joey
240 extractFieldsNameDeduplication 2023-11-02T15:47:42Z 0eda368d32dbd7f57635cabd6b7d6870ca7b58ee Oscar Kilhed
241 dashboardSceneForViewers 2023-11-02T19:02:25Z 6e80a3d59b14d9af235431d6acdce95835e2a792 Dominik Prokop
242 panelFilterVariable 2023-11-03T12:15:54Z 6bf4d0cbc6756ec8e8fb64b3a9d7b76f4c4194f5 Jacob Zelek
243 addFieldFromCalculationStatFunctions 2023-11-03T14:39:58Z 61d63d3034d7eed4837c63e1f5cb2f6b7114758d Victor Marin
244 pdfTables 2023-11-06T13:39:22Z 95b48339f89c7d267bce7d894404d26ccd75d0e3 Agnès Toulet
245 newVizTooltips 2023-11-06T16:35:59Z 2024-04-03T00:32:01Z 6b4b7127544865b78f712d907e6f1719595f4232 Adela Almasan
246 ssoSettingsApi 2023-11-08T09:50:01Z 5285e9503be5702680acb2b52a6bda0632f4603d Misi
247 logsInfiniteScrolling 2023-11-09T10:54:03Z 174c2ab45a2af912519153c5c3e671f04396d7d7 Matias Chomicki
248 flameGraphItemCollapsing 2023-11-09T14:31:07Z 494a07b522df4e3ff9512b47767745a94c15f080 Andrej Ocenas
249 alertingDetailsViewV2 2023-11-09T17:35:03Z 2024-03-14T14:18:01Z 323ee7c38ceb18b8e71c780d797fabac7041a673 Gilles De Mey
250 alertingSimplifiedRouting 2023-11-10T13:14:39Z 68e37c3925080cf64a5e7570eabb042f19ca2dbf Sonia Aguilar
251 dashboardScene 2023-11-13T08:51:21Z 4bc322ca1d6ed63d7e79eecb1ed09f3043f9aedb Torkel Ödegaard
252 datatrails 2023-11-15T11:28:29Z 2024-04-09T18:15:18Z 1f1d348e1700735683dc9b80fce106b8a5bc4cee Torkel Ödegaard
253 pluginsSkipHostEnvVars 2023-11-15T17:09:14Z cb0a88a02770eaee2561fd434d8339cab268f02e Giuseppe Guerra
254 logRowsPopoverMenu 2023-11-16T09:48:10Z 9cb303c3f701169e8651267744a4b5fe1695a066 Matias Chomicki
255 lokiStructuredMetadata 2023-11-16T16:06:14Z a01f8c5b42bb7937139b8d52e9723cc37e5f7ae6 Sven Grossmann
256 tracesEmbeddedFlameGraph 2023-11-23T13:36:53Z 2024-01-22T14:21:14Z 4f46fb412ca4e96b0b0ef3f462aead3fc146389e Joey
257 regressionTransformation 2023-11-24T14:49:16Z ab982e7bd36b86c94093bddcc31008f5dc49660e Oscar Kilhed
258 displayAnonymousStats 2023-11-29T16:58:41Z 2024-02-23T15:53:37Z 59bdff0280d52ca5d8918157d7697b9279b25501 Eric Leijonmarck
259 influxqlStreamingParser 2023-11-29T17:29:35Z 5845f140758473ab5ffe789bec4077032fd22839 ismail simsek
260 kubernetesSnapshots 2023-12-05T22:31:49Z 439edebcd605a1b63bf3a9b0ab5c2b83341cd5cd Ryan McKinley
261 grafanaAPIServerEnsureKubectlAccess 2023-12-06T20:21:21Z c4c9bfaf2e7fa12a8e453df0f089c8b4f914a3d3 Dan Cech
262 unifiedStorage 2023-12-06T20:21:21Z c4c9bfaf2e7fa12a8e453df0f089c8b4f914a3d3 Dan Cech
263 alertStateHistoryAnnotationsFromLoki 2023-12-11T19:17:01Z 2024-01-25T17:56:09Z 4c1bf86ae11696277025296b86e8514386b4bb31 William Wernert
264 tableSharedCrosshair 2023-12-13T09:33:14Z 5aff3389f4633d6970eb2b3629ac015e564c626c Victor Marin
265 lokiQueryHints 2023-12-18T20:43:16Z 2165c9b3f000f59c9fbda80d2bfe3bc74cd6d9dc Sven Grossmann
266 canvasPanelPanZoom 2024-01-02T19:52:21Z 2502fe4d19faa993da8fd4d21b9802d59f4b02af Drew Slobodnjak
267 alertingPreviewUpgrade 2024-01-05T18:31:05Z 2024-03-14T14:36:35Z 49891d6a72537c869628a13ab75c8d616fbac590 Matthew Jacobson
268 enablePluginsTracingByDefault 2024-01-10T11:25:54Z 2024-04-11T16:40:47Z b40d3e748717961ef68457442d71c1284189c577 Giuseppe Guerra
269 cloudRBACRoles 2024-01-10T13:19:01Z 5b3cd9f55b292f0b693f3bb5171da7b69dac4a95 Karl Persson
270 alertingQueryOptimization 2024-01-10T20:52:58Z afa33f12b2cf50d2c7e438f498d27b0684797778 Matthew Jacobson
271 newFolderPicker 2024-01-15T11:43:19Z ec53487c995777b314f566f5a1054e3f8e29ec05 Ashley Harrison
272 kubernetesFeatureToggles 2024-01-18T05:32:44Z 41e523bde7db5706f339d418c68d019039a8062e Ryan McKinley
273 returnToPrevious 2024-01-18T17:12:14Z 2024-05-27T15:47:57Z 5800e40fba2accf96d81328f000e35bbb7c7acf1 Laura Fernández
274 jitterAlertRulesWithinGroups 2024-01-18T18:48:11Z 00a260effab802edc8f72df50bfb6447aac343f0 Alexander Weaver
275 jitterAlertRules 2024-01-18T18:48:11Z 2024-02-09T21:53:58Z 00a260effab802edc8f72df50bfb6447aac343f0 Alexander Weaver
276 onPremToCloudMigrations 2024-01-22T16:09:08Z cf13cb9f70c2230f17450667ce59440304fb023c Michael Mandrus
277 alertingSaveStatePeriodic 2024-01-23T16:03:30Z aa25776f813926cb4f1947d4ae5a014f4e7728ff Jean-Philippe Quéméner
278 promQLScope 2024-01-29T20:22:17Z 43d0664340f3e3af219d7b5c747f486a073f5ce3 Kyle Brandt
279 slateAutocomplete 2024-01-31T10:01:20Z 2024-02-01T13:03:11Z 39057552dc2ddfe8ee770c67a71cf55159aca741 Ashley Harrison
280 nodeGraphDotLayout 2024-01-31T16:26:12Z cb945aa5df3453ab2a851ef7ec8bee244e3eeac4 Andrej Ocenas
281 kubernetesQueryServiceRewrite 2024-01-31T18:36:51Z 2024-04-19T09:26:21Z e013cd427cb0457177e11f19ebd30bc523b36c76 Ryan McKinley
282 influxdbRunQueriesInParallel 2024-02-01T10:58:24Z 536153c3362782ae7f79e06755e8c7e0cd3b3acd ismail simsek
283 groupToNestedTableTransformation 2024-02-07T14:28:26Z 756cd3c28b0648f55a6c0158f5faf63b632ebe70 Kyle Cunningham
284 newPDFRendering 2024-02-08T12:09:34Z 28e66b4ad82ecebb551374325f0be4332412341c Agnès Toulet
285 autoMigrateGraphPanel 2024-02-08T22:00:48Z 829672759c12b27f849c92c3a2aee4a6b0037920 Nathan Marrs
286 dashboardSceneSolo 2024-02-11T08:08:47Z fe6d1460b09b403fc74fd20e95f61513eede2555 Torkel Ödegaard
287 kubernetesAggregator 2024-02-12T20:59:35Z d6e6298103d5d6a4efd1c21a3d74f429b506f3a7 Todd Treece
288 autoMigrateStatPanel 2024-02-14T16:06:25Z ce750e06187599da6b9c0a91ef95c7a62fe0d069 Nathan Marrs
289 autoMigrateWorldmapPanel 2024-02-14T16:06:25Z ce750e06187599da6b9c0a91ef95c7a62fe0d069 Nathan Marrs
290 autoMigratePiechartPanel 2024-02-14T16:06:25Z ce750e06187599da6b9c0a91ef95c7a62fe0d069 Nathan Marrs
291 autoMigrateTablePanel 2024-02-14T16:06:25Z ce750e06187599da6b9c0a91ef95c7a62fe0d069 Nathan Marrs
292 groupByVariable 2024-02-14T17:18:04Z f016f95298fe490a865612864520f3622f8e804a Dominik Prokop
293 alertingUpgradeDryrunOnStart 2024-02-16T16:29:54Z 2024-03-14T14:36:35Z dfaf6d1e2e13b2bd11dc8f0cd4432bfcad819aa9 Matthew Jacobson
294 expressionParser 2024-02-17T00:59:11Z f23f50f58d7ab5cb1fd88b42b6c58ec09c1a159d Ryan McKinley
295 sqlExpressions 2024-02-27T21:16:00Z 70009201d44c2d0ab39cc77081808a69a6c4fd63 Scott Lepper
296 aiGeneratedDashboardChanges 2024-03-05T12:01:31Z a7c06d26f14b2a9fa8faa929a6c9a0c355018429 Ivan Ortega Alba
297 scopeFilters 2024-03-05T15:41:19Z b3efb4217e48656f24aacdffd5737595d7361afe Carl Bergquist
298 betterPageScrolling 2024-03-06T15:06:47Z 6a4e0c692ab26f4d4cb99ae615013e0b6e23f90b Josh Hunt
299 emailVerificationEnforcement 2024-03-11T14:09:44Z 2024-03-22T13:30:58Z 0b55d72fb5698e1ea2cf73eaceae166cd5619daa Karl Persson
300 ssoSettingsSAML 2024-03-14T11:04:45Z 831ee9ee1696c0aa7a6e4ca022ddfc0ab28b86dc linoman
301 publicDashboardsScene 2024-03-22T14:48:21Z 8d4ca72f2a0e66c446d58d8bf13fadbc988fce11 Juan Cabanas
302 autoMigrateXYChartPanel 2024-03-22T15:44:37Z d7fa99e2df8269425eefb6373a665d8ec0b219d9 Leon Sorokin
303 usePrometheusFrontendPackage 2024-03-23T00:47:53Z 2024-04-15T21:45:23Z d0845952117152e7707f964cc343790497220990 Brendan O'Handley
304 oauthRequireSubClaim 2024-03-25T13:22:24Z 2f3a01f79fda7f6c465dd64adf5cf5c7227f9d19 Karl Persson
305 authAPIAccessTokenAuth 2024-04-02T15:45:15Z 5340a6e548b1e5fcd3af66695ca34d7e943fd068 Jo
306 newDashboardWithFiltersAndGroupBy 2024-04-04T11:25:21Z 32b6ef9d153dc7def2c17b81b02cfb1c412eb315 Dominik Prokop
307 prometheusCodeModeMetricNamesSearch 2024-04-04T20:38:23Z 559fab9dc6a6c882e7d9621ba013c3a53347176a Nick Richmond
308 cloudWatchNewLabelParsing 2024-04-05T15:57:56Z 58f32150c262605d188874b88f44a7dece4b9388 Isabella Siu
309 exploreMetrics 2024-04-09T18:15:18Z 66c0fd4dcc3202e11f41b302d27894dc162fb288 Darren Janeczek
310 accessActionSets 2024-04-12T16:19:25Z 56f4664875047d6861ea3facbc94cd921e263950 Ieva
311 disableNumericMetricsSortingInExpressions 2024-04-16T14:52:47Z d3fee607e2818747ad02daf6b8c58cc801075076 Nick Richmond
312 queryServiceRewrite 2024-04-19T09:26:21Z 5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904 Ryan McKinley
313 queryServiceFromUI 2024-04-19T09:26:21Z 5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904 Ryan McKinley
314 queryService 2024-04-19T09:26:21Z 5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904 Ryan McKinley
315 grafanaManagedRecordingRules 2024-04-22T17:53:16Z c32953e52cf9fd46a462711bc23fde15a5c3b6bf Alexander Weaver
316 logsExploreTableDefaultVisualization 2024-05-02T15:28:15Z 840aeddbd1957117d9b62074c155e87bb4afd4b4 Galen Kistler
317 autofixDSUID 2024-05-03T11:32:07Z b6f899d953a0924760dc5fd5a3d40669c86d475d Andres Martinez Gotor
318 newDashboardSharingComponent 2024-05-03T15:02:18Z d1434fad3a68bc1d2b49725be31e45526e6ad349 Juan Cabanas
319 tlsMemcached 2024-05-09T19:12:08Z b009536329d110afd807ef2f27f2b7dcc7d310ba lean.dev
320 notificationBanner 2024-05-13T09:32:34Z f3953b4955c218cc4678e842faa3d3380fd0f8f7 Alex Khomenko
321 dualWritePlaylistsMode3 2024-05-14T12:11:56Z 2024-05-31T18:18:09Z 6836bfe1ea1bf62f4eb66dc1328a456183874f81 Arati R
322 dualWritePlaylistsMode2 2024-05-14T12:11:56Z 2024-05-31T18:18:09Z 6836bfe1ea1bf62f4eb66dc1328a456183874f81 Arati R
323 dashboardRestore 2024-05-16T17:36:26Z 42d75ac737d7ac001a6d53376e25512408a01db5 Ezequiel Victorero
324 datasourceProxyDisableRBAC 2024-05-21T13:05:16Z 0072e4a92d896df343d9586522d6f7533773da78 Aaron Godin
325 alertingDisableSendAlertsExternal 2024-05-23T12:29:19Z 8421919cb552b9e8dbd4ebba20bcc67bbc5e6b4f Steve Simpson
326 datasourceQueryTypes 2024-05-23T16:46:28Z 42b0f802de31f3d42ece68544c193a69ea381bc0 Ryan McKinley
327 alertingListViewV2 2024-05-24T14:40:49Z 99b5259cc1efa3f5c9dbb9b0153be14437818f38 Gilles De Mey
328 preserveDashboardStateWhenNavigating 2024-05-27T12:28:06Z 6e9543e0addab825f6755d4d02c5a15a1598a483 Dominik Prokop
329 alertingCentralAlertHistory 2024-05-29T15:01:38Z 289ce6185574df99acea37b86c2e0866ea5940fd Sonia Aguilar
330 pluginProxyPreserveTrailingSlash 2024-06-05T11:36:14Z fe3e5917f1bc83ab29b6a57578316e054718aa4a Marcus Efraimsson
331 kubernetesDashboards 2024-06-05T14:34:23Z 41e0430f83bf7db50c4caaa1472afa3bf3d5c2dc Ryan McKinley
332 azureMonitorPrometheusExemplars 2024-06-06T16:53:17Z c9778c3332aa93e5dd8bc3b894264e1955f0d593 Andreas Christou
File diff suppressed because it is too large Load Diff
@@ -9,6 +9,7 @@ import (
"log"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"testing"
@@ -107,6 +108,25 @@ func TestFeatureToggleFiles(t *testing.T) {
})
}
// Set the dates from git history
dates := readFlagDateInfo(t)
for idx, item := range current.Items {
found, ok := dates[item.Name]
if ok {
// current.Items[idx].ResourceVersion = fmt.Sprintf("%d", found.created.UnixMilli()+int64(idx))
current.Items[idx].CreationTimestamp = v1.NewTime(found.created)
if found.deleted != nil {
tmp := v1.NewTime(*found.deleted)
current.Items[idx].DeletionTimestamp = &tmp
}
}
}
// Sort by name -- will avoid more git conflicts
sort.Slice(current.Items, func(i, j int) bool {
return current.Items[i].Name < current.Items[j].Name
})
out, err := json.MarshalIndent(current, "", " ")
require.NoError(t, err)
@@ -189,6 +209,38 @@ func verifyFlagsConfiguration(t *testing.T) {
// acronyms can be configured as needed via `ConfigureAcronym` function from `./strcase/camel.go`
}
type flagDateInfo struct {
created time.Time
deleted *time.Time
}
// Load a cached copy of the feature toggle dates
func readFlagDateInfo(t *testing.T) map[string]flagDateInfo {
info := make(map[string]flagDateInfo, 300)
// This file is created by running the script in:
// https://github.com/grafana/grafana-enterprise/blob/ff-git-log-history/scripts/sidecar/main.go#L9
body, err := os.ReadFile("toggles-gitlog.csv")
require.NoError(t, err)
reader := csv.NewReader(bytes.NewBuffer(body))
rows, err := reader.ReadAll()
require.NoError(t, err)
for _, row := range rows {
if strings.HasPrefix(row[0], "#") {
continue
}
d := flagDateInfo{}
d.created, err = time.Parse(time.RFC3339, row[1])
require.NoError(t, err)
if row[2] != "" {
tmp, err := time.Parse(time.RFC3339, row[2])
require.NoError(t, err)
d.deleted = &tmp
}
info[row[0]] = d
}
return info
}
func verifyAndGenerateFile(t *testing.T, fpath string, gen string) {
// nolint:gosec
// We can ignore the gosec G304 warning since this is a test and the function is only called explicitly above
+1 -1
View File
@@ -90,7 +90,7 @@ func (e *Engine) Test(ctx context.Context, user identity.Requester, rule *models
start := time.Now()
tsField := data.NewField("Time", nil, make([]time.Time, length))
valueFields := make(map[string]*data.Field)
valueFields := make(map[data.Fingerprint]*data.Field)
err = evaluator.Eval(ruleCtx, from, time.Duration(rule.IntervalSeconds)*time.Second, length, func(idx int, currentTime time.Time, results eval.Results) error {
if idx >= length {
@@ -160,9 +160,10 @@ func TestNewBacktestingEvaluator(t *testing.T) {
func TestEvaluatorTest(t *testing.T) {
states := []eval.State{eval.Normal, eval.Alerting, eval.Pending}
generateState := func(prefix string) *state.State {
labels := models.GenerateAlertLabels(rand.Intn(5)+1, prefix+"-")
return &state.State{
CacheID: "state-" + prefix,
Labels: models.GenerateAlertLabels(rand.Intn(5)+1, prefix+"-"),
CacheID: labels.Fingerprint(),
Labels: labels,
State: states[rand.Intn(len(states))],
}
}
@@ -201,10 +202,11 @@ func TestEvaluatorTest(t *testing.T) {
var states []state.StateTransition
for _, s := range allStates {
labels := models.GenerateAlertLabels(rand.Intn(5)+1, s.String()+"-")
states = append(states, state.StateTransition{
State: &state.State{
CacheID: "state-" + s.String(),
Labels: models.GenerateAlertLabels(rand.Intn(5)+1, s.String()+"-"),
CacheID: labels.Fingerprint(),
Labels: labels,
State: s,
StateReason: util.GenerateShortUID(),
},
@@ -226,7 +228,7 @@ func TestEvaluatorTest(t *testing.T) {
require.Equal(t, data.FieldTypeTime, timestampField.Type())
})
fieldByState := make(map[string]*data.Field, len(states))
fieldByState := make(map[data.Fingerprint]*data.Field, len(states))
t.Run("should contain a field per state", func(t *testing.T) {
for _, s := range states {
@@ -269,11 +271,12 @@ func TestEvaluatorTest(t *testing.T) {
from := time.Unix(0, 0)
to := from.Add(5 * ruleInterval)
labels := models.GenerateAlertLabels(rand.Intn(5)+1, "test-")
states := []state.StateTransition{
{
State: &state.State{
CacheID: "state-1",
Labels: models.GenerateAlertLabels(rand.Intn(5)+1, "test-"),
CacheID: labels.Fingerprint(),
Labels: labels,
State: eval.Normal,
StateReason: util.GenerateShortUID(),
},
+46 -28
View File
@@ -141,9 +141,6 @@ type ExecutionResults struct {
// Results contains the results of all queries, reduce and math expressions
Results map[string]data.Frames
// Errors contains a map of RefIDs that returned an error
Errors map[string]error
// NoData contains the DatasourceUID for RefIDs that returned no data.
NoData map[string]string
@@ -166,16 +163,12 @@ func (evalResults Results) HasErrors() bool {
// HasNonRetryableErrors returns true if we have at least 1 result with:
// 1. A `State` of `Error`
// 2. The `Error` attribute is not nil
// 3. The `Error` type is of `&invalidEvalResultFormatError` or `ErrSeriesMustBeWide`
// 3. The `Error` matches IsNonRetryableError
// Our thinking with this approach, is that we don't want to retry errors that have relation with invalid alert definition format.
func (evalResults Results) HasNonRetryableErrors() bool {
for _, r := range evalResults {
if r.State == Error && r.Error != nil {
var nonRetryableError *invalidEvalResultFormatError
if errors.As(r.Error, &nonRetryableError) {
return true
}
if errors.Is(r.Error, expr.ErrSeriesMustBeWide) {
if IsNonRetryableError(r.Error) {
return true
}
}
@@ -183,6 +176,19 @@ func (evalResults Results) HasNonRetryableErrors() bool {
return false
}
// IsNonRetryableError indicates whether an error is considered persistent and not worth performing evaluation retries.
// Currently it is true if err is `&invalidEvalResultFormatError` or `ErrSeriesMustBeWide`
func IsNonRetryableError(err error) bool {
var nonRetryableError *invalidEvalResultFormatError
if errors.As(err, &nonRetryableError) {
return true
}
if errors.Is(err, expr.ErrSeriesMustBeWide) {
return true
}
return false
}
// HasErrors returns true when Results contains at least one element and all elements are errors
func (evalResults Results) IsError() bool {
for _, r := range evalResults {
@@ -407,7 +413,6 @@ type NumberValueCapture struct {
Value *float64
}
//nolint:gocyclo
func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.QueryDataResponse) ExecutionResults {
// captures contains the values of all instant queries and expressions for each dimension
captures := make(map[string]map[data.Fingerprint]NumberValueCapture)
@@ -433,17 +438,10 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q
}
result := ExecutionResults{Results: make(map[string]data.Frames)}
for refID, res := range execResp.Responses {
if res.Error != nil {
if result.Errors == nil {
result.Errors = make(map[string]error)
}
result.Errors[refID] = res.Error
if refID == c.Condition {
result.Error = res.Error
}
}
result.Error = FindConditionError(execResp, c.Condition)
for refID, res := range execResp.Responses {
// There are two possible frame formats for No Data:
//
// 1. A response with no frames
@@ -526,30 +524,50 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q
}
}
return result
}
// FindConditionError extracts the error from a query response that caused the given condition to fail.
// If a condition failed because a node it depends on had an error, that error is returned instead.
// It returns nil if there are no errors related to the condition.
func FindConditionError(resp *backend.QueryDataResponse, condition string) error {
if resp == nil {
return nil
}
errs := make(map[string]error)
for refID, node := range resp.Responses {
if node.Error != nil {
errs[refID] = node.Error
}
}
conditionErr := errs[condition]
// If the error of the condition is an Error that indicates the condition failed
// because one of its dependent query or expressions failed, then we follow
// the dependency chain to an error that is not a dependency error.
if len(result.Errors) > 0 && result.Error != nil {
if errors.Is(result.Error, expr.DependencyError) {
if conditionErr != nil {
if errors.Is(conditionErr, expr.DependencyError) {
var utilError errutil.Error
e := result.Error
e := conditionErr
for {
errors.As(e, &utilError)
depRefID := utilError.PublicPayload["depRefId"].(string)
depError, ok := result.Errors[depRefID]
depError, ok := errs[depRefID]
if !ok {
return result
return conditionErr
}
if !errors.Is(depError, expr.DependencyError) {
result.Error = depError
return result
conditionErr = depError
return conditionErr
}
e = depError
}
}
}
return result
return conditionErr
}
// datasourceUIDsToRefIDs returns a sorted slice of Ref IDs for each Datasource UID.
+79
View File
@@ -894,6 +894,85 @@ func TestEvaluate(t *testing.T) {
},
EvaluationString: "[ var='A' labels={foo=bar} value=10 ], [ var='B' labels={bar=baz, foo=bar} value=1 ]",
}},
}, {
name: "results contains error if condition frame has error",
cond: models.Condition{
Condition: "B",
},
resp: backend.QueryDataResponse{
Responses: backend.Responses{
"A": {
Frames: []*data.Frame{{
RefID: "A",
Fields: []*data.Field{
data.NewField(
"Value",
data.Labels{"foo": "bar"},
[]*float64{util.Pointer(10.0)},
),
},
}},
},
"B": {
Frames: []*data.Frame{{
RefID: "B",
Fields: []*data.Field{
data.NewField(
"Value",
data.Labels{"foo": "bar", "bar": "baz"},
[]*float64{util.Pointer(1.0)},
),
},
}},
Error: errors.New("some frame error"),
},
},
},
expected: Results{{
State: Error,
Error: errors.New("some frame error"),
EvaluationString: "",
}},
}, {
name: "results contain underlying error if condition frame has error that depends on another node",
cond: models.Condition{
Condition: "B",
},
resp: backend.QueryDataResponse{
Responses: backend.Responses{
"A": {
Frames: []*data.Frame{{
RefID: "A",
Fields: []*data.Field{
data.NewField(
"Value",
data.Labels{"foo": "bar"},
[]*float64{util.Pointer(10.0)},
),
},
}},
Error: errors.New("another error depends on me"),
},
"B": {
Frames: []*data.Frame{{
RefID: "B",
Fields: []*data.Field{
data.NewField(
"Value",
data.Labels{"foo": "bar", "bar": "baz"},
[]*float64{util.Pointer(1.0)},
),
},
}},
Error: expr.MakeDependencyError("B", "A"),
},
},
},
expected: Results{{
State: Error,
Error: errors.New("another error depends on me"),
EvaluationString: "",
}},
}}
for _, tc := range cases {
@@ -65,6 +65,10 @@ func (il *InstanceLabels) StringAndHash() (string, string, error) {
return string(b), fmt.Sprintf("%x", h.Sum(nil)), nil
}
func (il *InstanceLabels) Fingerprint() data.Fingerprint {
return data.Labels(*il).Fingerprint()
}
// The following is based on SDK code, copied for now
// tupleLables is an alternative representation of Labels (map[string]string) that can be sorted
@@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
)
@@ -39,6 +40,19 @@ func TestTupleLabelsToLabels(t *testing.T) {
})
}
func TestInstanceLabelsFingerprint(t *testing.T) {
t.Run("returns labels fingerprint", func(t *testing.T) {
labels := InstanceLabels{
"foo": "bar",
"baz": "qux",
}
fingerprint := labels.Fingerprint()
expectedFingerprint := data.Labels(labels).Fingerprint()
require.Equal(t, expectedFingerprint, fingerprint)
})
}
func BenchmarkTupleLabelsToLabels(b *testing.B) {
b.Run("10 labels", func(b *testing.B) {
in := make(tupleLabels, 0, 10)
@@ -215,7 +215,7 @@ func (moa *MultiOrgAlertmanager) setupClustering(cfg *setting.Cfg) error {
return fmt.Errorf("unable to initialize gossip mesh: %w", err)
}
err = peer.Join(alertingCluster.DefaultReconnectInterval, alertingCluster.DefaultReconnectTimeout)
err = peer.Join(alertingCluster.DefaultReconnectInterval, cfg.UnifiedAlerting.HAReconnectTimeout)
if err != nil {
moa.logger.Error("Msg", "Unable to join gossip mesh while initializing cluster for high availability mode", "error", err)
}
@@ -531,7 +531,7 @@ func TestRuleRoutine(t *testing.T) {
for i := 0; i < 2; i++ {
states = append(states, &state.State{
AlertRuleUID: rule.UID,
CacheID: util.GenerateShortUID(),
CacheID: data.Labels(rule.Labels).Fingerprint(),
OrgID: rule.OrgID,
State: s,
StartsAt: sch.clock.Now(),
+38 -24
View File
@@ -116,7 +116,10 @@ func (r *recordingRule) doEvaluate(ctx context.Context, ev *Evaluation) {
logger := r.logger.FromContext(ctx).New("now", ev.scheduledAt, "fingerprint", ev.Fingerprint())
orgID := fmt.Sprint(ev.rule.OrgID)
evalDuration := r.metrics.EvalDuration.WithLabelValues(orgID)
evalAttemptTotal := r.metrics.EvalAttemptTotal.WithLabelValues(orgID)
evalAttemptFailures := r.metrics.EvalAttemptFailures.WithLabelValues(orgID)
evalTotal := r.metrics.EvalTotal.WithLabelValues(orgID)
evalTotalFailures := r.metrics.EvalFailures.WithLabelValues(orgID)
evalStart := r.clock.Now()
defer func() {
@@ -139,6 +142,7 @@ func (r *recordingRule) doEvaluate(ctx context.Context, ev *Evaluation) {
))
defer span.End()
var latestError error
for attempt := int64(1); attempt <= r.maxAttempts; attempt++ {
logger := logger.New("attempt", attempt)
if ctx.Err() != nil {
@@ -147,49 +151,59 @@ func (r *recordingRule) doEvaluate(ctx context.Context, ev *Evaluation) {
return
}
evalAttemptTotal.Inc()
err := r.tryEvaluation(ctx, ev, logger)
latestError = err
if err == nil {
return
break
}
logger.Error("Failed to evaluate rule", "attempt", attempt, "error", err)
select {
case <-ctx.Done():
logger.Error("Context has been cancelled while backing off", "attempt", attempt)
return
case <-time.After(retryDelay):
continue
evalAttemptFailures.Inc()
if eval.IsNonRetryableError(err) {
break
}
if attempt < r.maxAttempts {
select {
case <-ctx.Done():
logger.Error("Context has been cancelled while backing off", "attempt", attempt)
return
case <-time.After(retryDelay):
continue
}
}
}
if latestError != nil {
evalTotalFailures.Inc()
span.SetStatus(codes.Error, "rule evaluation failed")
span.RecordError(latestError)
if r.maxAttempts > 0 {
logger.Error("Recording rule evaluation failed after all attempts", "lastError", latestError)
}
} else {
logger.Debug("Recording rule evaluation succeeded")
}
}
func (r *recordingRule) tryEvaluation(ctx context.Context, ev *Evaluation, logger log.Logger) error {
orgID := fmt.Sprint(ev.rule.OrgID)
evalAttemptTotal := r.metrics.EvalAttemptTotal.WithLabelValues(orgID)
evalAttemptFailures := r.metrics.EvalAttemptFailures.WithLabelValues(orgID)
evalTotalFailures := r.metrics.EvalFailures.WithLabelValues(orgID)
evalStart := r.clock.Now()
evalCtx := eval.NewContext(ctx, SchedulerUserFor(ev.rule.OrgID))
result, err := r.buildAndExecutePipeline(ctx, evalCtx, ev, logger)
evalDur := r.clock.Now().Sub(evalStart)
evalAttemptTotal.Inc()
span := trace.SpanFromContext(ctx)
// TODO: In some cases, err can be nil but the dataframe itself contains embedded error frames. Parse these out like we do when evaluating alert rules.
// TODO: (Maybe, refactor something in eval package so we can use shared code for this)
if err != nil {
evalAttemptFailures.Inc()
// TODO: Only errors embedded in the frame can be considered retryable.
// TODO: Since we are not handling these yet per the above TODO, we can blindly consider all errors to be non-retryable for now, and just exit.
evalTotalFailures.Inc()
span.SetStatus(codes.Error, "rule evaluation failed")
span.RecordError(err)
return fmt.Errorf("server side expressions pipeline returned an error: %w", err)
}
// There might be errors in the pipeline results, even if the query succeeded.
if err := eval.FindConditionError(result, ev.rule.Record.From); err != nil {
return fmt.Errorf("the query failed with an error: %w", err)
}
logger.Info("Recording rule evaluated", "results", result, "duration", evalDur)
span := trace.SpanFromContext(ctx)
span.AddEvent("rule evaluated", trace.WithAttributes(
attribute.Int64("results", int64(len(result.Responses))),
))
+6 -10
View File
@@ -20,7 +20,7 @@ import (
)
type ruleStates struct {
states map[string]*State
states map[data.Fingerprint]*State
}
type cache struct {
@@ -89,7 +89,7 @@ func (c *cache) getOrCreate(ctx context.Context, log log.Logger, alertRule *ngMo
}
var states *ruleStates
if states, ok = orgStates[stateCandidate.AlertRuleUID]; !ok {
states = &ruleStates{states: make(map[string]*State)}
states = &ruleStates{states: make(map[data.Fingerprint]*State)}
c.states[stateCandidate.OrgID][stateCandidate.AlertRuleUID] = states
}
return states.getOrAdd(stateCandidate)
@@ -199,18 +199,14 @@ func calculateState(ctx context.Context, log log.Logger, alertRule *ngModels.Ale
log.Warn("Evaluation result contains either reserved labels or labels declared in the rules. Those labels from the result will be ignored", "labels", dupes)
}
il := ngModels.InstanceLabels(lbs)
id, err := il.StringKey()
if err != nil {
log.Error("Error getting cacheId for entry", "error", err)
}
cacheID := lbs.Fingerprint()
// For new states, we set StartsAt & EndsAt to EvaluatedAt as this is the
// expected value for a Normal state during state transition.
newState := State{
AlertRuleUID: alertRule.UID,
OrgID: alertRule.OrgID,
CacheID: id,
CacheID: cacheID,
Labels: lbs,
Annotations: annotations,
EvaluationDuration: result.EvaluationDuration,
@@ -279,12 +275,12 @@ func (c *cache) set(entry *State) {
c.states[entry.OrgID] = make(map[string]*ruleStates)
}
if _, ok := c.states[entry.OrgID][entry.AlertRuleUID]; !ok {
c.states[entry.OrgID][entry.AlertRuleUID] = &ruleStates{states: make(map[string]*State)}
c.states[entry.OrgID][entry.AlertRuleUID] = &ruleStates{states: make(map[data.Fingerprint]*State)}
}
c.states[entry.OrgID][entry.AlertRuleUID].states[entry.CacheID] = entry
}
func (c *cache) get(orgID int64, alertRuleUID, stateId string) *State {
func (c *cache) get(orgID int64, alertRuleUID string, stateId data.Fingerprint) *State {
c.mtxStates.RLock()
defer c.mtxStates.RUnlock()
ruleStates, ok := c.states[orgID][alertRuleUID]
+4 -6
View File
@@ -180,15 +180,12 @@ func (st *Manager) Warm(ctx context.Context, rulesReader RuleReader) {
rulesStates, ok := orgStates[entry.RuleUID]
if !ok {
rulesStates = &ruleStates{states: make(map[string]*State)}
rulesStates = &ruleStates{states: make(map[data.Fingerprint]*State)}
orgStates[entry.RuleUID] = rulesStates
}
lbs := map[string]string(entry.Labels)
cacheID, err := entry.Labels.StringKey()
if err != nil {
st.log.Error("Error getting cacheId for entry", "error", err)
}
cacheID := entry.Labels.Fingerprint()
var resultFp data.Fingerprint
if entry.ResultFingerprint != "" {
fp, err := strconv.ParseUint(entry.ResultFingerprint, 16, 64)
@@ -214,11 +211,12 @@ func (st *Manager) Warm(ctx context.Context, rulesReader RuleReader) {
statesCount++
}
}
st.cache.setAllStates(states)
st.log.Info("State cache has been initialized", "states", statesCount, "duration", time.Since(startTime))
}
func (st *Manager) Get(orgID int64, alertRuleUID, stateId string) *State {
func (st *Manager) Get(orgID int64, alertRuleUID string, stateId data.Fingerprint) *State {
return st.cache.get(orgID, alertRuleUID, stateId)
}
@@ -276,7 +276,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
if !ok { // skip if nothing to assert
continue
}
expectedTransitionsMap := make(map[string]StateTransition, len(expectedTransitions))
expectedTransitionsMap := make(map[data.Fingerprint]StateTransition, len(expectedTransitions))
for i := range expectedTransitions {
patchState(alertRule, expectedTransitions[i].State)
expectedTransitionsMap[expectedTransitions[i].CacheID] = expectedTransitions[i]
@@ -294,7 +294,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
}
}
if len(expectedTransitionsMap) > 0 {
vals := make([]string, 0, len(expectedTransitionsMap))
vals := make([]data.Fingerprint, 0, len(expectedTransitionsMap))
for _, s := range expectedTransitionsMap {
vals = append(vals, s.CacheID)
}
@@ -2524,15 +2524,6 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
ngmodels.KeepLastErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.KeepLastErrState)),
}
cacheID := func(lbls data.Labels) string {
l := ngmodels.InstanceLabels(lbls)
r, err := l.StringKey()
if err != nil {
panic(err)
}
return r
}
type errorTestCase struct {
desc string
ruleMutators []ngmodels.AlertRuleMutator
@@ -2587,7 +2578,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Normal,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
State: eval.Error,
Error: datasourceError,
@@ -2748,7 +2739,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Normal,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
State: eval.Error,
Error: datasourceError,
@@ -2881,7 +2872,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Normal,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
State: eval.Error,
Error: datasourceError,
@@ -3028,7 +3019,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Error,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
Error: datasourceError,
State: eval.Normal,
@@ -3148,7 +3139,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Normal,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
State: eval.Error,
Error: datasourceError,
@@ -3244,7 +3235,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Pending,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
State: eval.Error,
Error: datasourceError,
@@ -3330,7 +3321,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
{
PreviousState: eval.Pending,
State: &State{
CacheID: cacheID(labels["system + rule"]),
CacheID: labels["system + rule"].Fingerprint(),
Labels: labels["system + rule + datasource-error"],
State: eval.Error,
Error: datasourceError,
@@ -3539,14 +3530,11 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
}
func setCacheID(s *State) *State {
if s.CacheID != "" {
if s.CacheID != 0 {
return s
}
il := ngmodels.InstanceLabels(s.Labels)
id, err := il.StringKey()
if err != nil {
panic(err)
}
s.CacheID = id
s.CacheID = s.Labels.Fingerprint()
return s
}
+17 -29
View File
@@ -973,13 +973,9 @@ func TestProcessEvalResults(t *testing.T) {
expectedAnnotations: 1,
expectedStates: []*state.State{
{
CacheID: func() string {
CacheID: func() data.Fingerprint {
lbls := models.InstanceLabels(labels["system + rule + labels1"])
r, err := lbls.StringKey()
if err != nil {
panic(err)
}
return r
return lbls.Fingerprint()
}(),
Labels: mergeLabels(labels["system + rule + labels1"], data.Labels{
"datasource_uid": "datasource_uid_1",
@@ -1317,7 +1313,7 @@ func TestProcessEvalResults(t *testing.T) {
states := st.GetStatesForRuleUID(tc.alertRule.OrgID, tc.alertRule.UID)
assert.Len(t, states, len(tc.expectedStates))
expectedStates := make(map[string]*state.State, len(tc.expectedStates))
expectedStates := make(map[data.Fingerprint]*state.State, len(tc.expectedStates))
for _, s := range tc.expectedStates {
// patch all optional fields of the expected state
setCacheID(s)
@@ -1404,12 +1400,11 @@ func TestProcessEvalResults(t *testing.T) {
require.NotEmpty(t, states)
savedStates := make(map[string]models.AlertInstance)
savedStates := make(map[data.Fingerprint]models.AlertInstance)
for _, op := range instanceStore.RecordedOps() {
switch q := op.(type) {
case models.AlertInstance:
cacheId, err := q.Labels.StringKey()
require.NoError(t, err)
cacheId := q.Labels.Fingerprint()
savedStates[cacheId] = q
}
}
@@ -1578,7 +1573,7 @@ func TestStaleResultsHandler(t *testing.T) {
}
func TestStaleResults(t *testing.T) {
getCacheID := func(t *testing.T, rule *models.AlertRule, result eval.Result) string {
getCacheID := func(t *testing.T, rule *models.AlertRule, result eval.Result) data.Fingerprint {
t.Helper()
labels := data.Labels{}
for key, value := range rule.Labels {
@@ -1588,14 +1583,12 @@ func TestStaleResults(t *testing.T) {
labels[key] = value
}
lbls := models.InstanceLabels(labels)
key, err := lbls.StringKey()
require.NoError(t, err)
return key
return lbls.Fingerprint()
}
checkExpectedStates := func(t *testing.T, actual []*state.State, expected map[string]struct{}) map[string]*state.State {
checkExpectedStates := func(t *testing.T, actual []*state.State, expected map[data.Fingerprint]struct{}) map[data.Fingerprint]*state.State {
t.Helper()
result := make(map[string]*state.State)
result := make(map[data.Fingerprint]*state.State)
require.Len(t, actual, len(expected))
for _, currentState := range actual {
_, ok := expected[currentState.CacheID]
@@ -1604,7 +1597,7 @@ func TestStaleResults(t *testing.T) {
}
return result
}
checkExpectedStateTransitions := func(t *testing.T, actual []state.StateTransition, expected map[string]struct{}) {
checkExpectedStateTransitions := func(t *testing.T, actual []state.StateTransition, expected map[data.Fingerprint]struct{}) {
t.Helper()
require.Len(t, actual, len(expected))
for _, currentState := range actual {
@@ -1643,7 +1636,7 @@ func TestStaleResults(t *testing.T) {
state2 := getCacheID(t, rule, initResults[1])
state3 := getCacheID(t, rule, initResults[2])
initStates := map[string]struct{}{
initStates := map[data.Fingerprint]struct{}{
state1: {},
state2: {},
state3: {},
@@ -1687,7 +1680,7 @@ func TestStaleResults(t *testing.T) {
t.Run("should remove stale states from cache", func(t *testing.T) {
currentStates = st.GetStatesForRuleUID(rule.OrgID, rule.UID)
checkExpectedStates(t, currentStates, map[string]struct{}{
checkExpectedStates(t, currentStates, map[data.Fingerprint]struct{}{
getCacheID(t, rule, results[0]): {},
})
})
@@ -1782,7 +1775,7 @@ func TestDeleteStateByRuleUID(t *testing.T) {
}
for _, tc := range testCases {
expectedStatesMap := make(map[string]*state.State, len(tc.expectedStates))
expectedStatesMap := make(map[data.Fingerprint]*state.State, len(tc.expectedStates))
for _, expectedState := range tc.expectedStates {
s := setCacheID(expectedState)
expectedStatesMap[s.CacheID] = s
@@ -1992,20 +1985,15 @@ func TestResetStateByRuleUID(t *testing.T) {
}
func setCacheID(s *state.State) *state.State {
if s.CacheID != "" {
if s.CacheID != 0 {
return s
}
il := models.InstanceLabels(s.Labels)
id, err := il.StringKey()
if err != nil {
panic(err)
}
s.CacheID = id
s.CacheID = s.Labels.Fingerprint()
return s
}
func stateSliceToMap(states []*state.State) map[string]*state.State {
result := make(map[string]*state.State, len(states))
func stateSliceToMap(states []*state.State) map[data.Fingerprint]*state.State {
result := make(map[data.Fingerprint]*state.State, len(states))
for _, s := range states {
setCacheID(s)
result[s.CacheID] = s
+1 -1
View File
@@ -56,7 +56,7 @@ func (a *SyncStatePersister) deleteAlertStates(ctx context.Context, states []Sta
for _, s := range states {
key, err := s.GetAlertInstanceKey()
if err != nil {
a.log.Error("Failed to delete alert instance with invalid labels", "cacheID", s.CacheID, "error", err)
a.log.Error("Failed to delete alert instance with invalid labels", "cacheID", s.CacheID, "labels", s.Labels.String(), "error", err)
continue
}
toDelete = append(toDelete, key)
+1 -1
View File
@@ -27,7 +27,7 @@ type State struct {
// CacheID is a unique, opaque identifier for the state, and is used to find the state
// in the state cache. It tends to be derived from the state's labels.
CacheID string
CacheID data.Fingerprint
// State represents the current state.
State eval.State
+6
View File
@@ -18,6 +18,7 @@ const (
alertmanagerDefaultClusterAddr = "0.0.0.0:9094"
alertmanagerDefaultPeerTimeout = 15 * time.Second
alertmanagerDefaultGossipInterval = alertingCluster.DefaultGossipInterval
alertmanagerDefaultReconnectTimeout = alertingCluster.DefaultReconnectTimeout
alertmanagerDefaultPushPullInterval = alertingCluster.DefaultPushPullInterval
alertmanagerDefaultConfigPollInterval = time.Minute
alertmanagerRedisDefaultMaxConns = 5
@@ -71,6 +72,7 @@ type UnifiedAlertingSettings struct {
HAPeers []string
HAPeerTimeout time.Duration
HAGossipInterval time.Duration
HAReconnectTimeout time.Duration
HAPushPullInterval time.Duration
HALabel string
HARedisClusterModeEnabled bool
@@ -217,6 +219,10 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
if err != nil {
return err
}
uaCfg.HAReconnectTimeout, err = gtime.ParseDuration(valueAsString(ua, "ha_reconnect_timeout", (alertmanagerDefaultReconnectTimeout).String()))
if err != nil {
return err
}
uaCfg.HAPushPullInterval, err = gtime.ParseDuration(valueAsString(ua, "ha_push_pull_interval", (alertmanagerDefaultPushPullInterval).String()))
if err != nil {
return err
@@ -25,6 +25,7 @@ func TestCfg_ReadUnifiedAlertingSettings(t *testing.T) {
require.Len(t, cfg.UnifiedAlerting.HAPeers, 0)
require.Equal(t, 200*time.Millisecond, cfg.UnifiedAlerting.HAGossipInterval)
require.Equal(t, time.Minute, cfg.UnifiedAlerting.HAPushPullInterval)
require.Equal(t, 6*time.Hour, cfg.UnifiedAlerting.HAReconnectTimeout)
}
// With peers set, it correctly parses them.
@@ -627,6 +627,18 @@ export class Mousetrap {
// keep a list of which sequences were matches for later
doNotReset[seq] = 1;
this._fireCallback(callbacks[i].callback, e, callbacks[i].combo, seq);
// When matching a callback, don't reset other callbacks that starts with this prefix
// This allows chaining of multiple shortcuts that share a prefix. e.g. if we have
// `t left` and `t right`, allow user to hit `t left`, `right` without resetting the sequence
const suffixPrefixIndex = seq.lastIndexOf(character);
const sequencePrefix = seq.slice(0, suffixPrefixIndex);
for (const [seq, level] of Object.entries(this._sequenceLevels)) {
if (level > 0 && seq.startsWith(sequencePrefix)) {
doNotReset[seq] = 1;
}
}
continue;
}
@@ -637,6 +649,14 @@ export class Mousetrap {
}
}
// Don't reset a sequence if this character is the start of a sequence that has already progressed.
// This allows `t left` to be hit immediately after a `t right`
for (const callback of this._callbacks[character] ?? []) {
if (callback.action === e.type && callback.seq && callback.level === 0) {
doNotReset[callback.seq] = 1;
}
}
// if the key you pressed matches the type of sequence without
// being a modifier (ie "keyup" or "keypress") then we should
// reset all sequences that were not matched by this event
@@ -675,6 +695,11 @@ export class Mousetrap {
}
const event: KeyboardEvent = rawEvent;
// Don't trigger shortcuts when a key is just held down
if (event.repeat) {
return;
}
// normalize e.which for key events
// @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
if (typeof event.which !== 'number') {
@@ -743,9 +768,9 @@ export class Mousetrap {
this._ignoreNextKeyup = characterFromEvent(e);
}
// weird race condition if a sequence ends with the key
// another sequence begins with
setTimeout(this._resetSequences, 10);
// Reset the sequence timer and allow for this shortcut to be
// triggered again just by repeating the last key
this._resetSequenceTimer();
};
// loop through keys one at a time and bind the appropriate callback
@@ -11,11 +11,11 @@ export function BrowseFilters() {
<div>
<ActionRow
showStarredFilter
showLayout
state={searchState}
getTagOptions={stateManager.getTagOptions}
getSortOptions={getGrafanaSearcher().getSortOptions}
sortPlaceholder={getGrafanaSearcher().sortPlaceholder}
includePanels={searchState.includePanels ?? false}
onLayoutChange={stateManager.onLayoutChange}
onStarredFilterChange={stateManager.onStarredFilterChange}
onSortChange={stateManager.onSortChange}
@@ -11,6 +11,8 @@ jest.mock('./ContentOutlineContext', () => ({
const scrollIntoViewMock = jest.fn();
const scrollerMock = document.createElement('div');
const unregisterMock = jest.fn();
const setup = (mergeSingleChild = false) => {
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
@@ -50,6 +52,7 @@ const setup = (mergeSingleChild = false) => {
title: 'Item 2-1',
ref: document.createElement('div'),
level: 'child',
onRemove: () => unregisterMock('item-2-1'),
},
{
id: 'item-2-2',
@@ -62,7 +65,7 @@ const setup = (mergeSingleChild = false) => {
},
],
register: jest.fn(),
unregister: jest.fn(),
unregister: unregisterMock,
});
return render(<ContentOutline scroller={scrollerMock} panelId="content-outline-container-1" />);
@@ -143,4 +146,16 @@ describe('<ContentOutline />', () => {
await userEvent.click(button);
expect(button.getAttribute('aria-controls')).toBe(sectionContent.id);
});
it('deletes item on delete button click', async () => {
setup();
const expandSectionChevrons = screen.getAllByRole('button', { name: 'Content outline item collapse button' });
// chevron for the second item
const button = expandSectionChevrons[1];
await userEvent.click(button);
const deleteButtons = screen.getAllByTestId('content-outline-item-delete-button');
await userEvent.click(deleteButtons[0]);
expect(unregisterMock).toHaveBeenCalledWith('item-2-1');
});
});
@@ -47,9 +47,11 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
(item) => item.children && !(item.mergeSingleChild && item.children?.length === 1) && item.children.length > 0
);
const outlineItemsHaveDeleteButton = outlineItems.some((item) => item.children?.some((child) => child.onRemove));
const [sectionsExpanded, setSectionsExpanded] = useState(() => {
return outlineItems.reduce((acc: { [key: string]: boolean }, item) => {
acc[item.id] = false;
acc[item.id] = !!item.expanded;
return acc;
}, {});
});
@@ -58,6 +60,10 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
let scrollValue = 0;
let el: HTMLElement | null | undefined = ref;
if (!el) {
return;
}
do {
scrollValue += el?.offsetTop || 0;
el = el?.offsetParent instanceof HTMLElement ? el.offsetParent : undefined;
@@ -158,7 +164,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
title={contentOutlineExpanded ? item.title : undefined}
contentOutlineExpanded={contentOutlineExpanded}
className={cx(styles.buttonStyles, {
[styles.justifyCenter]: !contentOutlineExpanded,
[styles.justifyCenter]: !contentOutlineExpanded && !outlineItemsHaveDeleteButton,
[styles.sectionHighlighter]: isChildActive(item, activeSectionChildId) && !contentOutlineExpanded,
})}
indentStyle={cx({
@@ -196,7 +202,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
contentOutlineExpanded={contentOutlineExpanded}
icon={contentOutlineExpanded ? undefined : item.icon}
className={cx(styles.buttonStyles, {
[styles.justifyCenter]: !contentOutlineExpanded,
[styles.justifyCenter]: !contentOutlineExpanded && !outlineItemsHaveDeleteButton,
[styles.sectionHighlighter]:
isChildActive(item, activeSectionChildId) && !contentOutlineExpanded,
})}
@@ -211,6 +217,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
isActive={shouldBeActive(child, activeSectionId, activeSectionChildId, sectionsExpanded)}
extraHighlight={child.highlight}
color={child.color}
onRemove={child.onRemove ? () => child.onRemove?.(child.id) : undefined}
/>
</div>
))}
@@ -257,10 +264,10 @@ const getStyles = (theme: GrafanaTheme2, expanded: boolean) => {
marginRight: expanded ? theme.spacing(0.5) : undefined,
}),
indentRoot: css({
paddingLeft: theme.spacing(4),
paddingLeft: theme.spacing(3),
}),
indentChild: css({
paddingLeft: expanded ? theme.spacing(7) : theme.spacing(4),
paddingLeft: expanded ? theme.spacing(5) : theme.spacing(2.75),
}),
itemWrapper: css({
display: 'flex',
@@ -275,7 +282,7 @@ const getStyles = (theme: GrafanaTheme2, expanded: boolean) => {
borderRight: `1px solid ${theme.colors.border.medium}`,
content: '""',
height: '100%',
left: 48,
left: theme.spacing(4.75),
position: 'absolute',
transform: 'translateX(50%)',
},
@@ -18,6 +18,7 @@ export interface ContentOutlineContextProps {
unregister: (id: string) => void;
unregisterAllChildren: (parentId: string, childType: ITEM_TYPES) => void;
updateOutlineItems: (newItems: ContentOutlineItemContextProps[]) => void;
updateItem: (id: string, properties: Partial<Omit<ContentOutlineItemContextProps, 'id'>>) => void;
}
interface ContentOutlineContextProviderProps {
@@ -141,8 +142,11 @@ export function ContentOutlineContextProvider({ children, refreshDependencies }:
ref = parent.ref;
}
const childrenUpdated = [...(parent.children || []), { ...outlineItem, id, ref }];
childrenUpdated.sort(sortElementsByDocumentPosition);
let childrenUpdated = [{ ...outlineItem, id, ref }, ...(parent.children || [])];
if (!outlineItem.childOnTop) {
childrenUpdated = sortItems(childrenUpdated);
}
newItems[parentIndex] = {
...parent,
@@ -175,6 +179,20 @@ export function ContentOutlineContextProvider({ children, refreshDependencies }:
setOutlineItems(newItems);
}, []);
const updateItem = useCallback((id: string, properties: Partial<Omit<ContentOutlineItemContextProps, 'id'>>) => {
setOutlineItems((prevItems) =>
prevItems.map((item) => {
if (item.id === id) {
return {
...item,
...properties,
};
}
return item;
})
);
}, []);
const unregisterAllChildren = useCallback((parentId: string, childType: ITEM_TYPES) => {
setOutlineItems((prevItems) =>
prevItems.map((item) => {
@@ -190,7 +208,8 @@ export function ContentOutlineContextProvider({ children, refreshDependencies }:
setOutlineItems((prevItems) => {
const newItems = [...prevItems];
for (const item of newItems) {
item.children?.sort(sortElementsByDocumentPosition);
const sortedItems = sortItems(item.children || []);
item.children = sortedItems;
}
return newItems;
});
@@ -198,14 +217,14 @@ export function ContentOutlineContextProvider({ children, refreshDependencies }:
return (
<ContentOutlineContext.Provider
value={{ outlineItems, register, unregister, updateOutlineItems, unregisterAllChildren }}
value={{ outlineItems, register, unregister, updateOutlineItems, unregisterAllChildren, updateItem }}
>
{children}
</ContentOutlineContext.Provider>
);
}
export function sortElementsByDocumentPosition(a: ContentOutlineItemContextProps, b: ContentOutlineItemContextProps) {
function sortElementsByDocumentPosition(a: ContentOutlineItemContextProps, b: ContentOutlineItemContextProps) {
if (a.ref && b.ref) {
const diff = a.ref.compareDocumentPosition(b.ref);
if (diff === Node.DOCUMENT_POSITION_PRECEDING) {
@@ -217,6 +236,22 @@ export function sortElementsByDocumentPosition(a: ContentOutlineItemContextProps
return 0;
}
function sortItems(outlineItems: ContentOutlineItemContextProps[]): ContentOutlineItemContextProps[] {
const [skipSort, sortable] = outlineItems.reduce<
[ContentOutlineItemContextProps[], ContentOutlineItemContextProps[]]
>(
(acc, item) => {
item.childOnTop ? acc[0].push(item) : acc[1].push(item);
return acc;
},
[[], []]
);
sortable.sort(sortElementsByDocumentPosition);
return [...skipSort, ...sortable];
}
export function useContentOutlineContext() {
return useContext(ContentOutlineContext);
}
@@ -35,6 +35,13 @@ export interface ContentOutlineItemBaseProps {
* Client can additionally mark filter actions as highlighted
*/
highlight?: boolean;
onRemove?: (id: string) => void;
/**
* Child that will always be on top of the list
* e.g. pinned log in Logs section
*/
childOnTop?: boolean;
expanded?: boolean;
}
interface ContentOutlineItemProps extends ContentOutlineItemBaseProps {
@@ -2,7 +2,7 @@ import { cx, css } from '@emotion/css';
import React, { ButtonHTMLAttributes, useEffect, useRef, useState } from 'react';
import { IconName, isIconName, GrafanaTheme2 } from '@grafana/data';
import { Icon, Tooltip, useTheme2 } from '@grafana/ui';
import { Button, Icon, Tooltip, useTheme2 } from '@grafana/ui';
import { TooltipPlacement } from '@grafana/ui/src/components/Tooltip';
type CommonProps = {
@@ -20,6 +20,7 @@ type CommonProps = {
sectionId?: string;
toggleCollapsed?: () => void;
color?: string;
onRemove?: () => void;
};
export type ContentOutlineItemButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
@@ -39,6 +40,7 @@ export function ContentOutlineItemButton({
sectionId,
toggleCollapsed,
color,
onRemove,
...rest
}: ContentOutlineItemButtonProps) {
const theme = useTheme2();
@@ -83,6 +85,15 @@ export function ContentOutlineItemButton({
</span>
)}
</button>
{onRemove && (
<Button
variant="destructive"
className={styles.deleteButton}
icon="times"
onClick={() => onRemove()}
data-testid="content-outline-item-delete-button"
/>
)}
</div>
);
@@ -117,20 +128,20 @@ const getStyles = (theme: GrafanaTheme2, color?: string) => {
display: 'flex',
alignItems: 'center',
flexGrow: 1,
gap: theme.spacing(1),
overflow: 'hidden',
gap: theme.spacing(0.25),
width: '100%',
overflow: 'hidden',
}),
button: css({
label: 'content-outline-item-button',
display: 'flex',
alignItems: 'center',
height: theme.spacing(theme.components.height.md),
padding: theme.spacing(0, 1),
gap: theme.spacing(1),
gap: theme.spacing(0.5),
color: theme.colors.text.secondary,
width: '100%',
background: 'transparent',
overflow: 'hidden',
border: 'none',
}),
collapseButton: css({
@@ -143,6 +154,7 @@ const getStyles = (theme: GrafanaTheme2, color?: string) => {
color: theme.colors.text.secondary,
background: 'transparent',
border: 'none',
overflow: 'hidden',
'&:hover': {
color: theme.colors.text.primary,
@@ -153,6 +165,8 @@ const getStyles = (theme: GrafanaTheme2, color?: string) => {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: theme.typography.bodySmall.fontSize,
marginLeft: theme.spacing(0.5),
}),
active: css({
backgroundColor: theme.colors.background.secondary,
@@ -193,5 +207,11 @@ const getStyles = (theme: GrafanaTheme2, color?: string) => {
left: '2px',
},
}),
deleteButton: css({
width: theme.spacing(1),
height: theme.spacing(1),
padding: theme.spacing(0.75, 0.75),
marginRight: theme.spacing(0.5),
}),
};
};
+8 -6
View File
@@ -188,8 +188,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
/**
* Used by Logs details.
* Returns true if all queries have the filter, otherwise false.
* TODO: In the future, we would like to return active filters based the query that produced the log line.
* Returns true if the query identified by `refId` has a filter with the provided key and value.
* @alpha
*/
isFilterLabelActive = async (key: string, value: string | number, refId?: string) => {
@@ -235,14 +234,14 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
/**
* Used by Logs Popover Menu.
*/
onClickFilterValue = (value: string | number, refId?: string) => {
onClickFilterString = (value: string | number, refId?: string) => {
this.onModifyQueries({ type: 'ADD_STRING_FILTER', options: { value: value.toString() } }, refId);
};
/**
* Used by Logs Popover Menu.
*/
onClickFilterOutValue = (value: string | number, refId?: string) => {
onClickFilterOutString = (value: string | number, refId?: string) => {
this.onModifyQueries({ type: 'ADD_STRING_FILTER_OUT', options: { value: value.toString() } }, refId);
};
@@ -437,8 +436,11 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
splitOpenFn={this.onSplitOpen('logs')}
scrollElement={this.scrollElement}
isFilterLabelActive={this.isFilterLabelActive}
onClickFilterValue={this.onClickFilterValue}
onClickFilterOutValue={this.onClickFilterOutValue}
onClickFilterString={this.onClickFilterString}
onClickFilterOutString={this.onClickFilterOutString}
onPinLineCallback={() => {
this.setState({ contentOutlineVisible: true });
}}
/>
</ContentOutlineItem>
);
+66 -7
View File
@@ -39,12 +39,14 @@ import {
InlineFieldRow,
InlineSwitch,
PanelChrome,
PopoverContent,
RadioButtonGroup,
SeriesVisibilityChangeMode,
Themeable2,
withTheme2,
} from '@grafana/ui';
import { mapMouseEventToMode } from '@grafana/ui/src/components/VizLegend/utils';
import { Trans } from 'app/core/internationalization';
import store from 'app/core/store';
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
import { InfiniteScroll } from 'app/features/logs/components/InfiniteScroll';
@@ -109,9 +111,10 @@ interface Props extends Themeable2 {
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
logsFrames?: DataFrame[];
range: TimeRange;
onClickFilterValue?: (value: string, refId?: string) => void;
onClickFilterOutValue?: (value: string, refId?: string) => void;
onClickFilterString?: (value: string, refId?: string) => void;
onClickFilterOutString?: (value: string, refId?: string) => void;
loadMoreLogs?(range: AbsoluteTimeRange): void;
onPinLineCallback?: () => void;
}
export type LogsVisualisationType = 'table' | 'logs';
@@ -132,6 +135,7 @@ interface State {
tableFrame?: DataFrame;
visualisationType?: LogsVisualisationType;
logsContainer?: HTMLDivElement;
pinLineButtonTooltipTitle?: PopoverContent;
}
// we need to define the order of these explicitly
@@ -156,6 +160,8 @@ const getDefaultVisualisationType = (): LogsVisualisationType => {
return 'logs';
};
const PINNED_LOGS_LIMIT = 3;
class UnthemedLogs extends PureComponent<Props, State> {
flipOrderTimer?: number;
cancelFlippingTimer?: number;
@@ -183,6 +189,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
tableFrame: undefined,
visualisationType: this.props.panelState?.logs?.visualisationType ?? getDefaultVisualisationType(),
logsContainer: undefined,
pinLineButtonTooltipTitle: 'Pin to content outline',
};
constructor(props: Props) {
@@ -652,6 +659,56 @@ class UnthemedLogs extends PureComponent<Props, State> {
this.topLogsRef.current?.scrollIntoView();
};
onPinToContentOutlineClick = (row: LogRowModel) => {
if (this.getPinnedLogsCount() === PINNED_LOGS_LIMIT) {
this.setState({
pinLineButtonTooltipTitle: (
<span style={{ display: 'flex', textAlign: 'center' }}>
❗️
<Trans i18nKey="explore.logs.maximum-pinned-logs">
Maximum of {{ PINNED_LOGS_LIMIT }} pinned logs reached. Unpin a log to add another.
</Trans>
</span>
),
});
return;
}
// find the Logs parent item
const logsParent = this.context?.outlineItems.find((item) => item.panelId === 'Logs' && item.level === 'root');
//update the parent's expanded state
if (logsParent) {
this.context?.updateItem(logsParent.id, { expanded: true });
}
this.context?.register({
icon: 'gf-logs',
title: 'Pinned log',
panelId: 'Logs',
level: 'child',
ref: null,
color: LogLevelColor[row.logLevel],
childOnTop: true,
onClick: () => this.onOpenContext(row, () => {}),
onRemove: (id: string) => {
this.context?.unregister(id);
if (this.getPinnedLogsCount() < PINNED_LOGS_LIMIT) {
this.setState({
pinLineButtonTooltipTitle: 'Pin to content outline',
});
}
},
});
this.props.onPinLineCallback?.();
};
getPinnedLogsCount = () => {
const logsParent = this.context?.outlineItems.find((item) => item.panelId === 'Logs' && item.level === 'root');
return logsParent?.children?.filter((child) => child.title === 'Pinned log').length ?? 0;
};
render() {
const {
width,
@@ -943,8 +1000,10 @@ class UnthemedLogs extends PureComponent<Props, State> {
scrollIntoView={this.scrollIntoView}
isFilterLabelActive={this.props.isFilterLabelActive}
containerRendered={!!this.state.logsContainer}
onClickFilterValue={this.props.onClickFilterValue}
onClickFilterOutValue={this.props.onClickFilterOutValue}
onClickFilterString={this.props.onClickFilterString}
onClickFilterOutString={this.props.onClickFilterOutString}
onPinLine={this.onPinToContentOutlineClick}
pinLineButtonTooltipTitle={this.state.pinLineButtonTooltipTitle}
/>
</InfiniteScroll>
</div>
@@ -952,9 +1011,9 @@ class UnthemedLogs extends PureComponent<Props, State> {
{!loading && !hasData && !scanning && (
<div className={styles.logRows}>
<div className={styles.noData}>
No logs found.
<Trans i18nKey="explore.logs.no-logs-found">No logs found.</Trans>
<Button size="sm" variant="secondary" onClick={this.onClickScan}>
Scan for older logs
<Trans i18nKey="explore.logs.scan-for-older-logs">Scan for older logs</Trans>
</Button>
</div>
</div>
@@ -964,7 +1023,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
<div className={styles.noData}>
<span>{scanText}</span>
<Button size="sm" variant="secondary" onClick={this.onClickStopScan}>
Stop scan
<Trans i18nKey="explore.logs.stop-scan">Stop scan</Trans>
</Button>
</div>
</div>
@@ -58,8 +58,9 @@ interface LogsContainerProps extends PropsFromRedux {
splitOpenFn: SplitOpen;
scrollElement?: HTMLDivElement;
isFilterLabelActive: (key: string, value: string, refId?: string) => Promise<boolean>;
onClickFilterValue: (value: string, refId?: string) => void;
onClickFilterOutValue: (value: string, refId?: string) => void;
onClickFilterString: (value: string, refId?: string) => void;
onClickFilterOutString: (value: string, refId?: string) => void;
onPinLineCallback?: () => void;
}
type DataSourceInstance =
@@ -282,6 +283,7 @@ class LogsContainer extends PureComponent<LogsContainerProps, LogsContainerState
exploreId,
logsVolume,
scrollElement,
onPinLineCallback,
} = this.props;
if (!logRows) {
@@ -350,8 +352,9 @@ class LogsContainer extends PureComponent<LogsContainerProps, LogsContainerState
scrollElement={scrollElement}
isFilterLabelActive={this.logDetailsFilterAvailable() ? this.props.isFilterLabelActive : undefined}
range={range}
onClickFilterValue={this.filterValueAvailable() ? this.props.onClickFilterValue : undefined}
onClickFilterOutValue={this.filterOutValueAvailable() ? this.props.onClickFilterOutValue : undefined}
onPinLineCallback={onPinLineCallback}
onClickFilterString={this.filterValueAvailable() ? this.props.onClickFilterString : undefined}
onClickFilterOutString={this.filterOutValueAvailable() ? this.props.onClickFilterOutString : undefined}
/>
</LogsCrossFadeTransition>
</>
@@ -15,9 +15,9 @@ test('Does not render if the filter functions are not defined', () => {
});
test('Renders copy and line contains filter', async () => {
const onClickFilterValue = jest.fn();
const onClickFilterString = jest.fn();
render(
<PopoverMenu selection="test" x={0} y={0} row={row} close={() => {}} onClickFilterValue={onClickFilterValue} />
<PopoverMenu selection="test" x={0} y={0} row={row} close={() => {}} onClickFilterString={onClickFilterString} />
);
expect(screen.getByText('Copy selection')).toBeInTheDocument();
@@ -25,12 +25,12 @@ test('Renders copy and line contains filter', async () => {
await userEvent.click(screen.getByText('Add as line contains filter'));
expect(onClickFilterValue).toHaveBeenCalledTimes(1);
expect(onClickFilterValue).toHaveBeenCalledWith('test', row.dataFrame.refId);
expect(onClickFilterString).toHaveBeenCalledTimes(1);
expect(onClickFilterString).toHaveBeenCalledWith('test', row.dataFrame.refId);
});
test('Renders copy and line does not contain filter', async () => {
const onClickFilterOutValue = jest.fn();
const onClickFilterOutString = jest.fn();
render(
<PopoverMenu
selection="test"
@@ -38,7 +38,7 @@ test('Renders copy and line does not contain filter', async () => {
y={0}
row={row}
close={() => {}}
onClickFilterOutValue={onClickFilterOutValue}
onClickFilterOutString={onClickFilterOutString}
/>
);
@@ -47,8 +47,8 @@ test('Renders copy and line does not contain filter', async () => {
await userEvent.click(screen.getByText('Add as line does not contain filter'));
expect(onClickFilterOutValue).toHaveBeenCalledTimes(1);
expect(onClickFilterOutValue).toHaveBeenCalledWith('test', row.dataFrame.refId);
expect(onClickFilterOutString).toHaveBeenCalledTimes(1);
expect(onClickFilterOutString).toHaveBeenCalledWith('test', row.dataFrame.refId);
});
test('Renders copy, line contains filter, and line does not contain filter', () => {
@@ -59,8 +59,8 @@ test('Renders copy, line contains filter, and line does not contain filter', ()
y={0}
row={row}
close={() => {}}
onClickFilterValue={() => {}}
onClickFilterOutValue={() => {}}
onClickFilterString={() => {}}
onClickFilterOutString={() => {}}
/>
);
@@ -78,8 +78,8 @@ test('Can be dismissed with escape', async () => {
y={0}
row={row}
close={close}
onClickFilterValue={() => {}}
onClickFilterOutValue={() => {}}
onClickFilterString={() => {}}
onClickFilterOutString={() => {}}
/>
);
@@ -11,8 +11,8 @@ interface PopoverMenuProps {
selection: string;
x: number;
y: number;
onClickFilterValue?: (value: string, refId?: string) => void;
onClickFilterOutValue?: (value: string, refId?: string) => void;
onClickFilterString?: (value: string, refId?: string) => void;
onClickFilterOutString?: (value: string, refId?: string) => void;
row: LogRowModel;
close: () => void;
}
@@ -20,8 +20,8 @@ interface PopoverMenuProps {
export const PopoverMenu = ({
x,
y,
onClickFilterValue,
onClickFilterOutValue,
onClickFilterString,
onClickFilterOutString,
selection,
row,
close,
@@ -42,7 +42,7 @@ export const PopoverMenu = ({
};
}, [close]);
const supported = onClickFilterValue || onClickFilterOutValue;
const supported = onClickFilterString || onClickFilterOutString;
if (!supported) {
return null;
@@ -59,21 +59,21 @@ export const PopoverMenu = ({
track('copy', selection.length, row.datasourceType);
}}
/>
{onClickFilterValue && (
{onClickFilterString && (
<Menu.Item
label="Add as line contains filter"
onClick={() => {
onClickFilterValue(selection, row.dataFrame.refId);
onClickFilterString(selection, row.dataFrame.refId);
close();
track('line_contains', selection.length, row.datasourceType);
}}
/>
)}
{onClickFilterOutValue && (
{onClickFilterOutString && (
<Menu.Item
label="Add as line does not contain filter"
onClick={() => {
onClickFilterOutValue(selection, row.dataFrame.refId);
onClickFilterOutString(selection, row.dataFrame.refId);
close();
track('line_does_not_contain', selection.length, row.datasourceType);
}}
@@ -0,0 +1,123 @@
import { Table, amendTable } from "./amendTimeSeries";
describe('amendTable', () => {
it('should append nextTable when there is no overlap (nextTable after prevTable)', () => {
const prevTable: Table = [
[1, 2, 5],
['a', 'b', 'e'],
];
const nextTable: Table = [
[6, 7, 8],
['f', 'g', 'h'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual([
[1, 2, 5, 6, 7, 8],
['a', 'b', 'e', 'f', 'g', 'h'],
]);
});
it('should prepend nextTable when there is no overlap (nextTable before prevTable)', () => {
const prevTable: Table = [
[3, 4, 5],
['c', 'd', 'e'],
];
const nextTable: Table = [
[1, 2],
['a', 'b'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual([
[1, 2, 3, 4, 5],
['a', 'b', 'c', 'd', 'e'],
]);
});
it('should fully replace prevTable when nextTable covers entire range', () => {
const prevTable: Table = [
[3, 4, 5],
['c', 'd', 'e'],
];
const nextTable: Table = [
[1, 2, 3, 4, 5, 6],
['a', 'b', 'c', 'd', 'e', 'f'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual(nextTable);
});
it('should partially replace prevTable when nextTable is within range', () => {
const prevTable: Table = [
[1, 2, 3, 4, 5],
['a', 'b', 'c', 'd', 'e'],
];
const nextTable: Table = [
[3, 4],
['x', 'y'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual([
[1, 2, 3, 4, 5],
['a', 'b', 'x', 'y', 'e'],
]);
});
it('should append nextTable with overlap', () => {
const prevTable: Table = [
[1, 2, 5],
['a', 'b', 'e'],
];
const nextTable: Table = [
[2, 3, 6],
['b', 'c', 'f'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual([
[1, 2, 3, 6],
['a', 'b', 'c', 'f'],
]);
});
it('should prepend nextTable with overlap', () => {
const prevTable: Table = [
[3, 4, 5],
['c', 'd', 'e'],
];
const nextTable: Table = [
[1, 4],
['a', 'd'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual([
[1, 4, 5],
['a', 'd', 'e'],
]);
});
it('should handle empty prevTable', () => {
const prevTable: Table = [[]];
const nextTable: Table = [
[1, 2, 3],
['a', 'b', 'c'],
];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual(nextTable);
});
it('should handle empty nextTable', () => {
const prevTable: Table = [
[1, 2, 3],
['a', 'b', 'c'],
];
const nextTable: Table = [[]];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual(prevTable);
});
it('should handle both tables being empty', () => {
const prevTable: Table = [[]];
const nextTable: Table = [[]];
const result = amendTable(prevTable, nextTable);
expect(result).toEqual([[]]);
});
});
@@ -35,6 +35,18 @@ export function amendTable(prevTable: Table, nextTable: Table): Table {
}
// partial replace
else if (nStart > pStart && nEnd < pEnd) {
// partial replace
let startIdx = closestIdx(nStart, prevTimes);
startIdx = prevTimes[startIdx] < nStart ? startIdx + 1 : startIdx;
let endIdx = closestIdx(nEnd, prevTimes);
endIdx = prevTimes[endIdx] > nEnd ? endIdx - 1 : endIdx;
outTable = prevTable.map((_, i) =>
prevTable[i]
.slice(0, startIdx)
.concat(nextTable[i])
.concat(prevTable[i].slice(endIdx + 1))
) as Table;
}
// append, with overlap
else if (nStart >= pStart) {
@@ -15,7 +15,7 @@ import {
} from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { DataQuery, TimeZone } from '@grafana/schema';
import { withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui';
import { withTheme2, Themeable2, Icon, Tooltip, PopoverContent } from '@grafana/ui';
import { checkLogsError, escapeUnescapedString } from '../utils';
@@ -60,6 +60,7 @@ interface Props extends Themeable2 {
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
onPinLine?: (row: LogRowModel) => void;
onUnpinLine?: (row: LogRowModel) => void;
pinLineButtonTooltipTitle?: PopoverContent;
pinned?: boolean;
containerRendered?: boolean;
handleTextSelection?: (e: MouseEvent<HTMLTableRowElement>, row: LogRowModel) => boolean;
@@ -305,6 +306,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
styles={styles}
onPinLine={this.props.onPinLine}
onUnpinLine={this.props.onUnpinLine}
pinLineButtonTooltipTitle={this.props.pinLineButtonTooltipTitle}
pinned={this.props.pinned}
mouseIsOver={this.state.mouseIsOver}
onBlur={this.onMouseLeave}
@@ -2,7 +2,7 @@ import React, { FocusEvent, SyntheticEvent, useCallback } from 'react';
import { LogRowContextOptions, LogRowModel, getDefaultTimeRange, locationUtil, urlUtil } from '@grafana/data';
import { DataQuery } from '@grafana/schema';
import { ClipboardButton, IconButton } from '@grafana/ui';
import { ClipboardButton, IconButton, PopoverContent } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { LogRowStyles } from './getLogRowStyles';
@@ -20,10 +20,12 @@ interface Props {
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
onPinLine?: (row: LogRowModel) => void;
onUnpinLine?: (row: LogRowModel) => void;
pinLineButtonTooltipTitle?: PopoverContent;
pinned?: boolean;
styles: LogRowStyles;
mouseIsOver: boolean;
onBlur: () => void;
onPinToContentOutlineClick?: (row: LogRowModel, onOpenContext: (row: LogRowModel) => void) => void;
}
export const LogRowMenuCell = React.memo(
@@ -33,6 +35,7 @@ export const LogRowMenuCell = React.memo(
onPermalinkClick,
onPinLine,
onUnpinLine,
pinLineButtonTooltipTitle,
pinned,
row,
showContextToggle,
@@ -145,7 +148,7 @@ export const LogRowMenuCell = React.memo(
size="md"
name="gf-pin"
onClick={() => onPinLine && onPinLine(row)}
tooltip="Pin line"
tooltip={pinLineButtonTooltipTitle ?? 'Pin line'}
tooltipPlacement="top"
aria-label="Pin line"
tabIndex={0}
@@ -3,6 +3,7 @@ import Highlighter from 'react-highlight-words';
import { CoreApp, findHighlightChunksInText, LogRowContextOptions, LogRowModel } from '@grafana/data';
import { DataQuery } from '@grafana/schema';
import { PopoverContent } from '@grafana/ui';
import { LogMessageAnsi } from './LogMessageAnsi';
import { LogRowMenuCell } from './LogRowMenuCell';
@@ -25,6 +26,7 @@ interface Props {
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
onPinLine?: (row: LogRowModel) => void;
onUnpinLine?: (row: LogRowModel) => void;
pinLineButtonTooltipTitle?: PopoverContent;
pinned?: boolean;
styles: LogRowStyles;
mouseIsOver: boolean;
@@ -88,6 +90,7 @@ export const LogRowMessage = React.memo((props: Props) => {
onPermalinkClick,
onUnpinLine,
onPinLine,
pinLineButtonTooltipTitle,
pinned,
mouseIsOver,
onBlur,
@@ -124,6 +127,7 @@ export const LogRowMessage = React.memo((props: Props) => {
onPermalinkClick={onPermalinkClick}
onPinLine={onPinLine}
onUnpinLine={onUnpinLine}
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
pinned={pinned}
styles={styles}
mouseIsOver={mouseIsOver}
@@ -221,8 +221,8 @@ describe('Popover menu', () => {
logsSortOrder={LogsSortOrder.Descending}
enableLogDetails={true}
displayedFields={[]}
onClickFilterOutValue={() => {}}
onClickFilterValue={() => {}}
onClickFilterOutString={() => {}}
onClickFilterString={() => {}}
app={app}
/>
);
@@ -15,7 +15,7 @@ import {
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { withTheme2, Themeable2 } from '@grafana/ui';
import { withTheme2, Themeable2, PopoverContent } from '@grafana/ui';
import { PopoverMenu } from '../../explore/Logs/PopoverMenu';
import { UniqueKeyMaker } from '../UniqueKeyMaker';
@@ -50,6 +50,7 @@ export interface Props extends Themeable2 {
onClickHideField?: (key: string) => void;
onPinLine?: (row: LogRowModel) => void;
onUnpinLine?: (row: LogRowModel) => void;
pinLineButtonTooltipTitle?: PopoverContent;
onLogRowHover?: (row?: LogRowModel) => void;
onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
getRowContextQuery?: (
@@ -68,8 +69,8 @@ export interface Props extends Themeable2 {
* Any overflowing content will be clipped at the table boundary.
*/
overflowingContent?: boolean;
onClickFilterValue?: (value: string, refId?: string) => void;
onClickFilterOutValue?: (value: string, refId?: string) => void;
onClickFilterString?: (value: string, refId?: string) => void;
onClickFilterOutString?: (value: string, refId?: string) => void;
}
interface State {
@@ -107,7 +108,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
if (!config.featureToggles.logRowsPopoverMenu || this.props.app !== CoreApp.Explore) {
return false;
}
return Boolean(this.props.onClickFilterOutValue || this.props.onClickFilterValue);
return Boolean(this.props.onClickFilterOutString || this.props.onClickFilterString);
}
handleSelection = (e: MouseEvent<HTMLTableRowElement>, row: LogRowModel): boolean => {
@@ -217,8 +218,8 @@ class UnThemedLogRows extends PureComponent<Props, State> {
row={this.state.selectedRow}
selection={this.state.selection}
{...this.state.popoverMenuCoordinates}
onClickFilterValue={rest.onClickFilterValue}
onClickFilterOutValue={rest.onClickFilterOutValue}
onClickFilterString={rest.onClickFilterString}
onClickFilterOutString={rest.onClickFilterOutString}
/>
)}
<table className={cx(styles.logsRowsTable, this.props.overflowingContent ? '' : styles.logsRowsTableContain)}>
@@ -238,6 +239,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
permalinkedRowId={this.props.permalinkedRowId}
onPinLine={this.props.onPinLine}
onUnpinLine={this.props.onUnpinLine}
pinLineButtonTooltipTitle={this.props.pinLineButtonTooltipTitle}
pinned={this.props.pinnedRowId === row.uid}
isFilterLabelActive={this.props.isFilterLabelActive}
handleTextSelection={this.popoverMenuSupported() ? this.handleSelection : undefined}
@@ -260,6 +262,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
permalinkedRowId={this.props.permalinkedRowId}
onPinLine={this.props.onPinLine}
onUnpinLine={this.props.onUnpinLine}
pinLineButtonTooltipTitle={this.props.pinLineButtonTooltipTitle}
pinned={this.props.pinnedRowId === row.uid}
isFilterLabelActive={this.props.isFilterLabelActive}
handleTextSelection={this.popoverMenuSupported() ? this.handleSelection : undefined}
@@ -1,9 +1,7 @@
import React, { memo, useEffect } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import { FilterInput } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { t } from 'app/core/internationalization';
import { ActionRow } from 'app/features/search/page/components/ActionRow';
import { getGrafanaSearcher } from 'app/features/search/service';
@@ -37,19 +35,11 @@ const RecentlyDeletedPage = memo(() => {
return (
<Page navId="dashboards/recentlyDeleted">
<Page.Contents>
<FilterInput
placeholder={t('recentlyDeleted.filter.placeholder', 'Search for dashboards')}
value={searchState.query}
escapeRegex={false}
onChange={(e) => stateManager.onQueryChange(e)}
/>
<ActionRow
showStarredFilter={false}
state={searchState}
getTagOptions={stateManager.getTagOptions}
getSortOptions={getGrafanaSearcher().getSortOptions}
sortPlaceholder={getGrafanaSearcher().sortPlaceholder}
includePanels={false}
onLayoutChange={stateManager.onLayoutChange}
onSortChange={stateManager.onSortChange}
onTagFilterChange={stateManager.onTagFilterChange}
@@ -21,21 +21,21 @@ function getLayoutOptions() {
];
}
interface Props {
interface ActionRowProps {
state: SearchState;
showStarredFilter?: boolean;
showLayout?: boolean;
sortPlaceholder?: string;
onLayoutChange: (layout: SearchLayout) => void;
onSortChange: (value?: string) => void;
onStarredFilterChange?: (event: FormEvent<HTMLInputElement>) => void;
onTagFilterChange: (tags: string[]) => void;
getTagOptions: () => Promise<TermCount[]>;
getSortOptions: () => Promise<SelectableValue[]>;
sortPlaceholder?: string;
onDatasourceChange: (ds?: string) => void;
onPanelTypeChange: (pt?: string) => void;
includePanels: boolean;
onSetIncludePanels: (v: boolean) => void;
state: SearchState;
showStarredFilter?: boolean;
hideLayout?: boolean;
}
export function getValidQueryLayout(q: SearchState): SearchLayout {
@@ -52,20 +52,20 @@ export function getValidQueryLayout(q: SearchState): SearchLayout {
}
export const ActionRow = ({
state,
showStarredFilter,
showLayout,
sortPlaceholder,
onLayoutChange,
onSortChange,
onStarredFilterChange = () => {},
onTagFilterChange,
getTagOptions,
getSortOptions,
sortPlaceholder,
onDatasourceChange,
onPanelTypeChange,
onSetIncludePanels,
state,
showStarredFilter,
hideLayout,
}: Props) => {
}: ActionRowProps) => {
const styles = useStyles2(getStyles);
const layout = getValidQueryLayout(state);
@@ -113,7 +113,7 @@ export const ActionRow = ({
</Stack>
<Stack gap={2}>
{!hideLayout && (
{showLayout && (
<RadioButtonGroup
options={getLayoutOptions()}
disabledOptions={disabledOptions}
@@ -92,12 +92,13 @@ export const BarChartPanel = (props: PanelProps<Options>) => {
const xGroupsCount = vizSeries[0]?.length ?? 0;
const seriesCount = vizSeries[0]?.fields.length ?? 0;
const totalSeries = info.series[0].fields.length - 1;
let { builder, prepData } = useMemo(
() => {
return xGroupsCount === 0
? { builder: null, prepData: null }
: prepConfig({ series: vizSeries, color: info.color, orientation, options, timeZone, theme });
: prepConfig({ series: vizSeries, totalSeries, color: info.color, orientation, options, timeZone, theme });
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
@@ -105,6 +106,7 @@ export const BarChartPanel = (props: PanelProps<Options>) => {
timeZone,
props.data.structureRev,
totalSeries,
seriesCount,
xGroupsCount,
@@ -94,6 +94,7 @@ describe('BarChart utils', () => {
describe('preparePlotConfigBuilder', () => {
const config: PrepConfigOpts = {
series: [mockDataFrame()],
totalSeries: 2,
// color?: Field | null;
timeZone: defaultTimeZone,
theme: createTheme(),
+8 -4
View File
@@ -148,7 +148,8 @@ export function prepSeries(
}
export interface PrepConfigOpts {
series: DataFrame[];
series: DataFrame[]; // series with hideFrom.viz: false
totalSeries: number; // total series count (including hidden)
color?: Field | null;
orientation: VizOrientation;
options: Options;
@@ -156,7 +157,7 @@ export interface PrepConfigOpts {
theme: GrafanaTheme2;
}
export const prepConfig = ({ series, color, orientation, options, timeZone, theme }: PrepConfigOpts) => {
export const prepConfig = ({ series, totalSeries, color, orientation, options, timeZone, theme }: PrepConfigOpts) => {
let {
showValue,
groupWidth,
@@ -205,8 +206,11 @@ export const prepConfig = ({ series, color, orientation, options, timeZone, them
const vizOrientation = getScaleOrientation(orientation);
// Use bar width when only one field
if (frame.fields.length === 2) {
groupWidth = barWidth;
if (frame.fields.length === 2 && stacking === StackingMode.None) {
if (totalSeries === 1) {
groupWidth = barWidth;
}
barWidth = 1;
}
@@ -324,6 +324,63 @@ describe('LogsPanel', () => {
expect(jest.mocked(styles.getLogRowStyles).mock.calls.length).toBeGreaterThan(3);
});
});
describe('Filters', () => {
const series = [
createDataFrame({
refId: 'A',
fields: [
{
name: 'time',
type: FieldType.time,
values: ['2019-04-26T09:28:11.352440161Z'],
},
{
name: 'message',
type: FieldType.string,
values: ['logline text'],
labels: {
app: 'common_app',
},
},
],
}),
];
it('allow to filter for a value or filter out a value', async () => {
const filterForMock = jest.fn();
const filterOutMock = jest.fn();
const isFilterLabelActiveMock = jest.fn();
setup({
data: {
series,
},
options: {
showLabels: false,
showTime: false,
wrapLogMessage: false,
showCommonLabels: false,
prettifyLogMessage: false,
sortOrder: LogsSortOrder.Descending,
dedupStrategy: LogsDedupStrategy.none,
enableLogDetails: true,
onClickFilterLabel: filterForMock,
onClickFilterOutLabel: filterOutMock,
isFilterLabelActive: isFilterLabelActiveMock,
},
});
expect(await screen.findByRole('row')).toBeInTheDocument();
await userEvent.click(screen.getByText('logline text'));
await userEvent.click(screen.getByLabelText('Filter for value in query A'));
expect(filterForMock).toHaveBeenCalledTimes(1);
await userEvent.click(screen.getByLabelText('Filter out value in query A'));
expect(filterOutMock).toHaveBeenCalledTimes(1);
expect(isFilterLabelActiveMock).toHaveBeenCalledTimes(1);
});
});
});
const setup = (propsOverrides?: {}) => {
+65 -3
View File
@@ -29,10 +29,34 @@ import { LogLabels } from '../../../features/logs/components/LogLabels';
import { LogRows } from '../../../features/logs/components/LogRows';
import { COMMON_LABELS, dataFrameToLogsModel, dedupLogRows } from '../../../features/logs/logsModel';
import { Options } from './types';
import {
isIsFilterLabelActive,
isOnClickFilterLabel,
isOnClickFilterOutLabel,
isOnClickFilterOutString,
isOnClickFilterString,
Options,
} from './types';
import { useDatasourcesFromTargets } from './useDatasourcesFromTargets';
interface LogsPanelProps extends PanelProps<Options> {}
interface LogsPanelProps extends PanelProps<Options> {
/**
* Adds a key => value filter to the query referenced by the provided DataFrame refId. Used by Log details and Logs table.
* onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void;
*
* Adds a negative key => value filter to the query referenced by the provided DataFrame refId. Used by Log details and Logs table.
* onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void;
*
* Adds a string filter to the query referenced by the provided DataFrame refId. Used by the Logs popover menu.
* onClickFilterOutString?: (value: string, refId?: string) => void;
*
* Removes a string filter to the query referenced by the provided DataFrame refId. Used by the Logs popover menu.
* onClickFilterString?: (value: string, refId?: string) => void;
*
* Determines if a given key => value filter is active in a given query. Used by Log details.
* isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
*/
}
interface LogsPermalinkUrlState {
logs?: {
id?: string;
@@ -55,6 +79,11 @@ export const LogsPanel = ({
dedupStrategy,
enableLogDetails,
showLogContextToggle,
onClickFilterLabel,
onClickFilterOutLabel,
onClickFilterOutString,
onClickFilterString,
isFilterLabelActive,
},
id,
}: LogsPanelProps) => {
@@ -68,7 +97,7 @@ export const LogsPanel = ({
const [scrollElement, setScrollElement] = useState<HTMLDivElement | null>(null);
let closeCallback = useRef<() => void>();
const { eventBus } = usePanelContext();
const { eventBus, onAddAdHocFilter } = usePanelContext();
const onLogRowHover = useCallback(
(row?: LogRowModel) => {
if (!row) {
@@ -220,6 +249,28 @@ export const LogsPanel = ({
[scrollElement]
);
const defaultOnClickFilterLabel = useCallback(
(key: string, value: string) => {
onAddAdHocFilter?.({
key,
value,
operator: '=',
});
},
[onAddAdHocFilter]
);
const defaultOnClickFilterOutLabel = useCallback(
(key: string, value: string) => {
onAddAdHocFilter?.({
key,
value,
operator: '!=',
});
},
[onAddAdHocFilter]
);
if (!data || logRows.length === 0) {
return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
}
@@ -275,6 +326,17 @@ export const LogsPanel = ({
onLogRowHover={onLogRowHover}
app={CoreApp.Dashboard}
onOpenContext={onOpenContext}
onClickFilterLabel={
isOnClickFilterLabel(onClickFilterLabel) ? onClickFilterLabel : defaultOnClickFilterLabel
}
onClickFilterOutLabel={
isOnClickFilterOutLabel(onClickFilterOutLabel) ? onClickFilterOutLabel : defaultOnClickFilterOutLabel
}
onClickFilterString={isOnClickFilterString(onClickFilterString) ? onClickFilterString : undefined}
onClickFilterOutString={
isOnClickFilterOutString(onClickFilterOutString) ? onClickFilterOutString : undefined
}
isFilterLabelActive={isIsFilterLabelActive(isFilterLabelActive) ? isFilterLabelActive : undefined}
/>
{showCommonLabels && isAscending && renderCommonLabels()}
</div>
@@ -35,6 +35,12 @@ composableKinds: PanelCfg: {
enableLogDetails: bool
sortOrder: common.LogsSortOrder
dedupStrategy: common.LogsDedupStrategy
// TODO: figure out how to define callbacks
onClickFilterLabel?: _
onClickFilterOutLabel?: _
isFilterLabelActive?: _
onClickFilterString?: _
onClickFilterOutString?: _
} @cuetsy(kind="interface")
}
}]
@@ -13,6 +13,14 @@ import * as common from '@grafana/schema';
export interface Options {
dedupStrategy: common.LogsDedupStrategy;
enableLogDetails: boolean;
isFilterLabelActive?: unknown;
/**
* TODO: figure out how to define callbacks
*/
onClickFilterLabel?: unknown;
onClickFilterOutLabel?: unknown;
onClickFilterOutString?: unknown;
onClickFilterString?: unknown;
prettifyLogMessage: boolean;
showCommonLabels: boolean;
showLabels: boolean;
+28
View File
@@ -1 +1,29 @@
import { DataFrame } from '@grafana/data';
export { Options } from './panelcfg.gen';
type onClickFilterLabelType = (key: string, value: string, frame?: DataFrame) => void;
type onClickFilterOutLabelType = (key: string, value: string, frame?: DataFrame) => void;
type onClickFilterValueType = (value: string, refId?: string) => void;
type onClickFilterOutStringType = (value: string, refId?: string) => void;
type isFilterLabelActiveType = (key: string, value: string, refId?: string) => Promise<boolean>;
export function isOnClickFilterLabel(callback: unknown): callback is onClickFilterLabelType {
return typeof callback === 'function';
}
export function isOnClickFilterOutLabel(callback: unknown): callback is onClickFilterOutLabelType {
return typeof callback === 'function';
}
export function isOnClickFilterString(callback: unknown): callback is onClickFilterValueType {
return typeof callback === 'function';
}
export function isOnClickFilterOutString(callback: unknown): callback is onClickFilterOutStringType {
return typeof callback === 'function';
}
export function isIsFilterLabelActive(callback: unknown): callback is isFilterLabelActiveType {
return typeof callback === 'function';
}
+6 -5
View File
@@ -497,6 +497,12 @@
"title": "Add query to Query Library",
"visibility": "Visibility"
},
"logs": {
"maximum-pinned-logs": "Maximum of {{PINNED_LOGS_LIMIT}} pinned logs reached. Unpin a log to add another.",
"no-logs-found": "No logs found.",
"scan-for-older-logs": "Scan for older logs",
"stop-scan": "Stop scan"
},
"rich-history": {
"close-tooltip": "Close query history",
"datasource-a-z": "Data source A-Z",
@@ -1479,11 +1485,6 @@
},
"query-editor-not-exported": "Data source plugin does not export any Query Editor component"
},
"recentlyDeleted": {
"filter": {
"placeholder": "Search for dashboards"
}
},
"refresh-picker": {
"aria-label": {
"choose-interval": "Auto refresh turned off. Choose refresh time interval",
+6 -5
View File
@@ -497,6 +497,12 @@
"title": "Åđđ qūęřy ŧő Qūęřy Ŀįþřäřy",
"visibility": "Vįşįþįľįŧy"
},
"logs": {
"maximum-pinned-logs": "Mäχįmūm őƒ {{PINNED_LOGS_LIMIT}} pįʼnʼnęđ ľőģş řęäčĥęđ. Ůʼnpįʼn ä ľőģ ŧő äđđ äʼnőŧĥęř.",
"no-logs-found": "Ńő ľőģş ƒőūʼnđ.",
"scan-for-older-logs": "Ŝčäʼn ƒőř őľđęř ľőģş",
"stop-scan": "Ŝŧőp şčäʼn"
},
"rich-history": {
"close-tooltip": "Cľőşę qūęřy ĥįşŧőřy",
"datasource-a-z": "Đäŧä şőūřčę Å-Ż",
@@ -1479,11 +1485,6 @@
},
"query-editor-not-exported": "Đäŧä şőūřčę pľūģįʼn đőęş ʼnőŧ ęχpőřŧ äʼny Qūęřy Ēđįŧőř čőmpőʼnęʼnŧ"
},
"recentlyDeleted": {
"filter": {
"placeholder": "Ŝęäřčĥ ƒőř đäşĥþőäřđş"
}
},
"refresh-picker": {
"aria-label": {
"choose-interval": "Åūŧő řęƒřęşĥ ŧūřʼnęđ őƒƒ. Cĥőőşę řęƒřęşĥ ŧįmę įʼnŧęřväľ",