Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync

This commit is contained in:
Mariell Hoversholm
2024-11-28 15:31:03 +01:00
222 changed files with 5528 additions and 4452 deletions
+12 -5
View File
@@ -1731,7 +1731,9 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"]
],
"public/app/features/alerting/unified/home/Insights.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/features/alerting/unified/home/PluginIntegrations.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
@@ -1742,9 +1744,10 @@ exports[`better eslint`] = {
"public/app/features/alerting/unified/hooks/useControlledFieldArray.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/alerting/unified/insights/RatingModal.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
"public/app/features/alerting/unified/insights/InsightsMenuButton.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/features/alerting/unified/mocks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@@ -3814,7 +3817,8 @@ exports[`better eslint`] = {
"public/app/features/sandbox/TestStuffPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
],
"public/app/features/scopes/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./instance\`)", "0"],
@@ -5452,6 +5456,9 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
"packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
"packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
+2 -2
View File
@@ -472,7 +472,7 @@
"name": "type/build-packaging",
"action": "addToProject",
"addToProject": {
"url": "https://github.com/orgs/grafana/projects/509"
"url": "https://github.com/orgs/grafana/projects/632"
}
},
{
@@ -616,7 +616,7 @@
"name": "area/security",
"action": "addToProject",
"addToProject": {
"url": "https://github.com/orgs/grafana/projects/470"
"url": "https://github.com/orgs/grafana/projects/476"
}
},
{
+1 -1
View File
@@ -13,7 +13,7 @@
"slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"@types/slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"@types/slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"storybook-dark-mode", // 4.0.2 causes storybook 8.4 to break with react hooks errors
// Temporarily pause updating lerna and nx until we resolve build issues
"lerna",
"nx"
@@ -265,7 +265,7 @@ jobs:
- name: Send Slack Message via Payload
id: slack
if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 && github.repository == 'grafana/grafana'
uses: grafana/shared-workflows/actions/send-slack-message@main
uses: grafana/shared-workflows/actions/send-slack-message@7b628e7352c2dea057c565cc4fcd5564d5f396c0 #v1.0.0
with:
channel-id: "C031SLFH6G0"
payload: |
+2 -1
View File
@@ -144,6 +144,7 @@ pkg/services/quota/quotaimpl/storage/storage.json
# Ignoring frontend packages specifics
/packages/grafana-ui/.yarn/.cache
/packages/grafana-ui/.storybook/static
/packages/**/dist
/packages/**/compiled
/packages/**/.rpt2_cache
@@ -228,4 +229,4 @@ public/app/plugins/**/dist/
# Mock service worker used for fake API responses in frontend development
public/mockServiceWorker.js
/e2e/test-plugins/*/dist
/e2e/test-plugins/*/dist
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -59,10 +59,10 @@ require (
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.6.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
+4 -4
View File
@@ -141,8 +141,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -153,8 +153,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
+3
View File
@@ -351,6 +351,9 @@ data_source_proxy_whitelist =
# disable protection against brute force login attempts
disable_brute_force_login_protection = false
# max number of failed login attempts before user gets locked
brute_force_login_protection_max_attempts = 5
# set to true if you host Grafana behind HTTPS. default is false.
cookie_secure = false
+3
View File
@@ -350,6 +350,9 @@
# disable protection against brute force login attempts
;disable_brute_force_login_protection = false
# max number of failed login attempts before user gets locked
;brute_force_login_protection_max_attempts = 5
# set to true if you host Grafana behind HTTPS. default is false.
;cookie_secure = false
@@ -181,13 +181,24 @@ It does not support absolute time ranges: `2021-12-02 00:00:00 to 2021-12-05 23:
1. Click **Set as alert condition** on the query or expression you want to set as your alert condition.
{{< /collapse >}}
## Add folders and labels
Organize your alert rule with a folder and set of labels.
In the **Labels** section, you can optionally choose whether to add labels to organize your alert rules, make searching easier, as well as set which notification policy should handle your firing alert instance.
1. Select a folder or click **+ New folder**.
1. Add labels, if required.
Add custom labels by selecting existing key-value pairs from the drop down, or add new labels by entering the new key or value.
## Set alert evaluation behavior
Use alert rule evaluation to determine how frequently an alert rule should be evaluated and how quickly it should change its state.
To do this, you need to make sure that your alert rule is in the right evaluation group and set a pending period time that works best for your use case.
1. Select a folder or click **+ New folder**.
1. Select an evaluation group or click **+ New evaluation group**.
If you are creating a new evaluation group, specify the interval for the group.
@@ -212,19 +223,13 @@ To do this, you need to make sure that your alert rule is in the right evaluatio
Use the guidelines in [No data and error handling](#configure-no-data-and-error-handling).
## Configure labels and notifications
## Configure notifications
In the **Labels** section, you can optionally choose whether to add labels to organize your alert rules, make searching easier, as well as set which notification policy should handle your firing alert instance.
Choose to select a contact point directly from the alert rule form or to use notification policy routing as well as set up mute timings and groupings.
In the **Configure notifications** section, you can choose to select a contact point directly from the alert rule form or choose to use notification policy routing as well as set up mute timings and groupings.
Complete the following steps to set up notifications.
Complete the following steps to set up labels and notifications.
1. Add labels, if required.
Add custom labels by selecting existing key-value pairs from the drop down, or add new labels by entering the new key or value.
2. Configure who receives a notification when an alert rule fires by either choosing **Select contact point** or **Use notification policy**.
1. Configure who receives a notification when an alert rule fires by either choosing **Select contact point** or **Use notification policy**.
**Select contact point**
@@ -232,30 +237,30 @@ Complete the following steps to set up labels and notifications.
All notifications for this alert rule are sent to this contact point automatically and notification policies are not used.
2. You can also optionally select a mute timing as well as groupings and timings to define when not to send notifications.
1. You can also optionally select a mute timing as well as groupings and timings to define when not to send notifications.
{{< admonition type="note" >}}
An auto-generated notification policy is generated. Only admins can view these auto-generated policies from the **Notification policies** list view. Any changes have to be made in the alert rules form. {{< /admonition >}}
**Use notification policy**
3. Choose this option to use the notification policy tree to direct your notifications.
1. Choose this option to use the notification policy tree to direct your notifications.
{{< admonition type="note" >}}
All alert rules and instances, irrespective of their labels, match the default notification policy. If there are no nested policies, or no nested policies match the labels in the alert rule or alert instance, then the default notification policy is the matching policy.
{{< /admonition >}}
4. Preview your alert instance routing set up.
1. Preview your alert instance routing set up.
Based on the labels added, alert instances are routed to the following notification policies displayed.
5. Expand each notification policy below to view more details.
1. Expand each notification policy below to view more details.
6. Click **See details** to view alert routing details and an email preview.
1. Click **See details** to view alert routing details and an email preview.
## Add annotations
## Configure notification message
Add [annotations](ref:annotation-label). to provide more context on the alert in your alert notification message.
Add more context on the alert in your alert notification message. For more information, see [annotations](ref:annotation-label).
Annotations add metadata to provide more information on the alert in your alert notification message. For example, add a **Summary** annotation to tell you which value caused the alert to fire or which server it happened on.
@@ -70,6 +70,7 @@ Watch the following video for a quick tutorial on creating annotations:
To add an annotation, complete the following steps:
1. If you've just saved a dashboard, refresh the page.
1. Click **Edit** in the top-right corner of the dashboard.
1. Click the panel to which you're adding the annotation.
@@ -85,6 +86,7 @@ Alternatively, to add an annotation, press Ctrl/Cmd and click the panel, and the
### Add a region annotation
1. If you've just saved a dashboard, refresh the page.
1. Click **Edit** in the top-right corner of the dashboard.
1. Press Ctrl/Cmd and click and drag on the panel.
![Add annotation popover](/static/img/docs/time-series-panel/time-series-annotations-add-region-annotation.gif)
@@ -52,9 +52,9 @@ refs:
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/
add-a-data-source:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#add-a-data-source
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#add-a-data-source
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#add-a-data-source
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#add-a-data-source
about-users-and-permissions:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/
@@ -215,107 +215,79 @@ Learn more about the kind of information provided in the [dashboard insights doc
Externally shared dashboards _should_ work with any data source that has the properties `backend` and `alerting` both set to true in its `plugin.json`. However, this can't always be
guaranteed because plugin developers can override this functionality. The following lists include data sources confirmed to work with externally shared dashboards and data sources that should work, but have not been confirmed as compatible.
### Confirmed:
### Confirmed
<table>
<tr>
<td>
<ul>
<li>ClickHouse</li>
<li>CloudWatch</li>
<li>Elasticsearch</li>
<li>Infinity</li>
<li>InfluxDB</li>
<li>Loki</li>
<li>Microsoft SQL Server</li>
</ul>
</td>
<td>
<ul>
<li>MongoDB</li>
<li>MySQL</li>
<li>Oracle Database</li>
<li>PostgreSQL</li>
<li>Prometheus</li>
<li>Redis</li>
<li>SQLite</li>
</ul>
</td>
</tr>
</table>
{{< column-list >}}
### Unsupported:
- ClickHouse
- CloudWatch
- Elasticsearch
- Infinity
- InfluxDB
- Loki
- Microsoft SQL Server
- MongoDB
- MySQL
- Oracle Database
- PostgreSQL
- Prometheus
- Redis
- SQLite
<table>
<tr>
<td>
<ul>
<li>Graphite</li>
</ul>
</td>
</tr>
</table>
{{< /column-list >}}
### Unconfirmed:
### Unsupported
<table>
<tr>
<td>
<ul>
<li>Altinity plugin for ClickHouse</li>
<li>Amazon Athena</li>
<li>Amazon Redshift</li>
<li>Amazon Timestream</li>
<li>Apache Cassandra</li>
<li>AppDynamics</li>
<li>Azure Data Explorer Datasource</li>
<li>Azure Monitor</li>
<li>CSV</li>
<li>DB2 Datasource</li>
<li>Databricks</li>
<li>Datadog</li>
<li>Dataset</li>
<li>Druid</li>
</ul>
</td>
<td>
<ul>
<li>Dynatrace</li>
<li>GitHub</li>
<li>Google BigQuery</li>
<li>Grafana for YNAB</li>
<li>Honeycomb</li>
<li>Jira</li>
<li>Mock</li>
<li>Neo4j Datasource</li>
<li>New Relic</li>
<li>OPC UA (Unified Architecture)</li>
<li>Open Distro for Elasticsearch</li>
<li>OpenSearch</li>
<li>OpenTSDB</li>
</ul>
</td>
<td>
<ul>
<li>Orbit</li>
<li>SAP HANA®</li>
<li>Salesforce</li>
<li>Sentry</li>
<li>ServiceNow</li>
<li>Snowflake</li>
<li>Splunk</li>
<li>Splunk Infrastructure Monitoring</li>
<li>Sqlyze data source</li>
<li>TDengine</li>
<li>Vertica</li>
<li>Wavefront</li>
<li>X-Ray</li>
<li>kdb+</li>
<li>simple grpc data source</li>
</ul>
</td>
</tr>
</table>
- Graphite
### Unconfirmed
{{< column-list >}}
- Altinity plugin for ClickHouse
- Amazon Athena
- Amazon Redshift
- Amazon Timestream
- Apache Cassandra
- AppDynamics
- Azure Data Explorer Datasource
- Azure Monitor
- CSV
- DB2 Datasource
- Databricks
- Datadog
- Dataset
- Druid
- Dynatrace
- GitHub
- Google BigQuery
- Grafana for YNAB
- Honeycomb
- Jira
- Mock
- Neo4j Datasource
- New Relic
- OPC UA (Unified Architecture)
- Open Distro for Elasticsearch
- OpenSearch
- OpenTSDB
- Orbit
- SAP HANA®
- Salesforce
- Sentry
- ServiceNow
- Snowflake
- Splunk
- Splunk Infrastructure Monitoring
- Sqlyze data source
- TDengine
- Vertica
- Wavefront
- X-Ray
- kdb+
- simple grpc data source
{{< /column-list >}}
## Limitations
@@ -143,14 +143,26 @@ If there's only one data link in the visualization, clicking anywhere on the vis
You can configure data links for the following visualizations:
| | | |
| ------------------------------ | ------------------------------------ | ------------------------------------ |
| [Bar chart](ref:bar-chart) | [Heatmap](ref:heatmap) | [Status history](ref:status-history) |
| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) |
| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) |
| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) |
| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | [XY chart](ref:xy-chart) |
| [Geomap](ref:geomap) | | |
{{< column-list >}}
- [Bar chart](ref:bar-chart)
- [Bar gauge](ref:bar-gauge)
- [Candlestick](ref:candlestick)
- [Canvas](ref:canvas)
- [Gauge](ref:gauge)
- [Geomap](ref:geomap)
- [Heatmap](ref:heatmap)
- [Histogram](ref:histogram)
- [Pie chart](ref:pie-chart)
- [Stat](ref:stat)
- [State timeline](ref:state-timeline)
- [Status history](ref:status-history)
- [Table](ref:table)
- [Time series](ref:time-series)
- [Trend](ref:trend)
- [XY chart](ref:xy-chart)
{{< /column-list >}}
## Data link variables
@@ -121,14 +121,26 @@ After you've set them, your overrides appear in both the **All** and **Overrides
You can configure field overrides for the following visualizations:
| | | |
| ------------------------------ | ------------------------------------ | ------------------------------------ |
| [Bar chart](ref:bar-chart) | [Heatmap](ref:heatmap) | [Status history](ref:status-history) |
| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) |
| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) |
| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) |
| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | [XY chart](ref:xy-chart) |
| [Geomap](ref:geomap) | | |
{{< column-list >}}
- [Bar chart](ref:bar-chart)
- [Bar gauge](ref:bar-gauge)
- [Candlestick](ref:candlestick)
- [Canvas](ref:canvas)
- [Gauge](ref:gauge)
- [Geomap](ref:geomap)
- [Heatmap](ref:heatmap)
- [Histogram](ref:histogram)
- [Pie chart](ref:pie-chart)
- [Stat](ref:stat)
- [State timeline](ref:state-timeline)
- [Status history](ref:status-history)
- [Table](ref:table)
- [Time series](ref:time-series)
- [Trend](ref:trend)
- [XY chart](ref:xy-chart)
{{< /column-list >}}
## Override rules
@@ -126,13 +126,25 @@ For more granular control over the display of fields, refer to [Configure overri
You can configure standard options for the following visualizations:
| | | |
| ------------------------------ | ------------------------------------ | ------------------------------------ |
| [Bar chart](ref:bar-chart) | [Geomap](ref:geomap) | [Status history](ref:status-history) |
| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) |
| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) |
| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) |
| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | [XY chart](ref:xy-chart) |
{{< column-list >}}
- [Bar chart](ref:bar-chart)
- [Bar gauge](ref:bar-gauge)
- [Candlestick](ref:candlestick)
- [Canvas](ref:canvas)
- [Gauge](ref:gauge)
- [Geomap](ref:geomap)
- [Histogram](ref:histogram)
- [Pie chart](ref:pie-chart)
- [Stat](ref:stat)
- [State timeline](ref:state-timeline)
- [Status history](ref:status-history)
- [Table](ref:table)
- [Time series](ref:time-series)
- [Trend](ref:trend)
- [XY chart](ref:xy-chart)
{{< /column-list >}}
## Standard options
@@ -110,13 +110,23 @@ You can also use thresholds to:
You can set thresholds in the following visualizations:
| | | |
| ------------------------------ | ------------------------------------ | ------------------------------------ |
| [Bar chart](ref:bar-chart) | [Geomap](ref:geomap) | [Status history](ref:status-history) |
| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) |
| [Candlestick](ref:candlestick) | [Stat](ref:stat) | [Time series](ref:time-series) |
| [Canvas](ref:canvas) | [State timeline](ref:state-timeline) | [Trend](ref:trend) |
| [Gauge](ref:gauge) |
{{< column-list >}}
- [Bar chart](ref:bar-chart)
- [Bar gauge](ref:bar-gauge)
- [Candlestick](ref:candlestick)
- [Canvas](ref:canvas)
- [Gauge](ref:gauge)
- [Geomap](ref:geomap)
- [Histogram](ref:histogram)
- [Stat](ref:stat)
- [State timeline](ref:state-timeline)
- [Status history](ref:status-history)
- [Table](ref:table)
- [Time series](ref:time-series)
- [Trend](ref:trend)
{{< /column-list >}}
## Default thresholds
@@ -84,13 +84,19 @@ When you hover your cursor over a visualization, Grafana can display tooltips th
You can configure tooltips for the following visualizations:
| | |
| ------------------------------------ | ------------------------------------ |
| [Bar chart](ref:bar-chart) | [Status history](ref:status-history) |
| [Candlestick](ref:candlestick) | [Time series](ref:time-series) |
| [Heatmap](ref:heatmap) | [Trend](ref:trend) |
| [Pie chart](ref:pie-chart) | [XY chart](ref:xy-chart) |
| [State timeline](ref:state-timeline) | |
{{< column-list >}}
- [Bar chart](ref:bar-chart)
- [Candlestick](ref:candlestick)
- [Heatmap](ref:heatmap)
- [Pie chart](ref:pie-chart)
- [State timeline](ref:state-timeline)
- [Status history](ref:status-history)
- [Time series](ref:time-series)
- [Trend](ref:trend)
- [XY chart](ref:xy-chart)
{{< /column-list >}}
Some visualizations, for example [candlestick](ref:candlestick) and [flame graph](ref:flame-graph), have tooltips, but they aren't configurable. These visualizations don't have a **Tooltip** section in the panel editor pane. [Geomaps](ref:geomaps) provide you the option to have tooltips triggered upon click or hover under the **Map controls** options in the panel editor pane.
@@ -105,13 +105,24 @@ Value mappings bypass unit formatting set in the **Standard options** section of
You can configure value mappings for the following visualizations:
| | | |
| ------------------------------ | ------------------------------------ | ------------------------------------ |
| [Bar chart](ref:bar-chart) | [Geomap](ref:geomap) | [Status history](ref:status-history) |
| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) |
| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) |
| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) |
| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | |
{{< column-list >}}
- [Bar chart](ref:bar-chart)
- [Bar gauge](ref:bar-gauge)
- [Candlestick](ref:candlestick)
- [Canvas](ref:canvas)
- [Gauge](ref:gauge)
- [Geomap](ref:geomap)
- [Histogram](ref:histogram)
- [Pie chart](ref:pie-chart)
- [Stat](ref:stat)
- [State timeline](ref:state-timeline)
- [Status history](ref:status-history)
- [Table](ref:table)
- [Time series](ref:time-series)
- [Trend](ref:trend)
{{< /column-list >}}
## Types of value mappings
@@ -630,7 +630,11 @@ Define a whitelist of allowed IP addresses or domains, with ports, to be used in
### disable_brute_force_login_protection
Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `false`. An existing user's account will be locked after 5 attempts in 5 minutes.
Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `false`. An existing user's account will be unable to login for 5 minutes if all login attempts are spent within a 5 minute window.
### brute_force_login_protection_max_attempts
Configure how many login attempts a user have within a 5 minute window before the account will be locked. Default is `5`.
### cookie_secure
@@ -81,6 +81,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
## Public preview feature toggles
@@ -189,6 +190,7 @@ Experimental features might be changed or removed without prior notice.
| `newFolderPicker` | Enables the nested folder picker without having nested folders enabled |
| `onPremToCloudMigrationsAlerts` | Enables the migration of alerts and its child resources to your Grafana Cloud stack. Requires `onPremToCloudMigrations` to be enabled in conjunction. |
| `onPremToCloudMigrationsAuthApiMig` | Enables the use of auth api instead of gcom for internal token services. Requires `onPremToCloudMigrations` to be enabled in conjunction. |
| `scopeApi` | In-development feature flag for the scope api using the app platform. |
| `sqlExpressions` | Enables using SQL and DuckDB functions as Expressions. |
| `nodeGraphDotLayout` | Changed the layout algorithm for the node graph |
| `kubernetesAggregator` | Enable grafana's embedded kube-aggregator |
+6 -6
View File
@@ -121,7 +121,7 @@ require (
github.com/m3db/prometheus_remote_client_golang v0.4.4 // @grafana/grafana-backend-group
github.com/madflojo/testcerts v1.1.1 // @grafana/alerting-backend
github.com/magefile/mage v1.15.0 // @grafana/grafana-developer-enablement-squad
github.com/matryer/is v1.4.0 // @grafana/grafana-as-code
github.com/matryer/is v1.4.1 // @grafana/grafana-as-code
github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group
github.com/mattn/go-sqlite3 v1.14.22 // @grafana/grafana-backend-group
github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend
@@ -171,15 +171,15 @@ require (
go.uber.org/atomic v1.11.0 // @grafana/alerting-backend
go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage
gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad
golang.org/x/crypto v0.28.0 // @grafana/grafana-backend-group
golang.org/x/crypto v0.29.0 // @grafana/grafana-backend-group
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // @grafana/alerting-backend
golang.org/x/mod v0.21.0 // indirect; @grafana/grafana-backend-group
golang.org/x/net v0.30.0 // @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/mod v0.22.0 // indirect; @grafana/grafana-backend-group
golang.org/x/net v0.31.0 // @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/oauth2 v0.23.0 // @grafana/identity-access-team
golang.org/x/sync v0.9.0 // @grafana/alerting-backend
golang.org/x/text v0.20.0 // @grafana/grafana-backend-group
golang.org/x/time v0.6.0 // @grafana/grafana-backend-group
golang.org/x/tools v0.26.0 // @grafana/grafana-as-code
golang.org/x/tools v0.27.0 // @grafana/grafana-as-code
gonum.org/v1/gonum v0.15.1 // @grafana/observability-metrics
google.golang.org/api v0.193.0 // @grafana/grafana-backend-group
google.golang.org/grpc v1.67.1 // @grafana/plugins-platform-backend
@@ -431,7 +431,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // @grafana/identity-access-team
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect; @grafana/grafana-backend-group
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
+12 -12
View File
@@ -2682,8 +2682,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM=
@@ -3518,8 +3518,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -3597,8 +3597,8 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -3697,8 +3697,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -3933,8 +3933,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -4064,8 +4064,8 @@ golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+5
View File
@@ -989,6 +989,9 @@ github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10 h1:oDbLKM34O+JUF9
github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4=
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I=
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw=
github.com/grafana/cog v0.0.4/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs=
github.com/grafana/cog v0.0.5 h1:BCa+10i3KvV+KMSQuxlN1DS9cZEwN+EAFc7ZmXqHxQE=
github.com/grafana/cog v0.0.5/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs=
github.com/grafana/cuetsy v0.1.10/go.mod h1:Ix97+CPD8ws9oSSxR3/Lf4ahU1I4Np83kjJmDVnLZvc=
github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak=
github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90=
@@ -1783,6 +1786,7 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
@@ -1796,6 +1800,7 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+21 -20
View File
@@ -6,46 +6,47 @@ import (
"context"
"os"
"cuelang.org/go/cue/cuecontext"
"github.com/grafana/cog"
)
type codegenTargets struct {
schemaPath string
outputPath string
modulePath string
outputPath string
cueImportsMap map[string]string
packagesImportMap map[string]string
}
func main() {
cueCtx := cuecontext.New()
targets := []codegenTargets{
{
schemaPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue",
modulePath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/",
outputPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts",
cueImportsMap: map[string]string{
"github.com/grafana/grafana/packages/grafana-schema/src/common": "../packages/grafana-schema/src/common",
},
packagesImportMap: map[string]string{
"common": "@grafana/schema",
},
},
}
for _, target := range targets {
rawSchema, err := os.ReadFile(target.schemaPath)
if err != nil {
panic(err)
}
value := cueCtx.CompileBytes(rawSchema)
if value.Err() != nil {
panic(value.Err())
}
codegenPipeline := cog.TypesFromSchema().
CUEValue("dashboard", value).
Typescript()
CUEModule(
target.modulePath,
cog.CUEImports(target.cueImportsMap),
).
Typescript(cog.TypescriptConfig{
ImportsMap: target.packagesImportMap,
EnumsAsUnionTypes: true,
})
tsBytes, err := codegenPipeline.Run(context.Background())
files, err := codegenPipeline.Run(context.Background())
if err != nil {
panic(err)
}
if err := os.WriteFile(target.outputPath, tsBytes, 0644); err != nil {
if err := os.WriteFile(target.outputPath, files[0].Data, 0644); err != nil {
panic(err)
}
}
+5 -7
View File
@@ -2,13 +2,11 @@ module github.com/grafana/grafana/kindsv2
go 1.23.1
require (
cuelang.org/go v0.8.2
github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070
)
require github.com/grafana/cog v0.0.5
require (
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect
cuelang.org/go v0.8.2 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/proto v1.13.2 // indirect
@@ -36,11 +34,11 @@ require (
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/yalue/merged_fs v1.3.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/tools v0.27.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+10 -10
View File
@@ -26,8 +26,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070 h1:ncAkFmIq3UbPiPhQ2CuBzdTuwnDqLsFFNqEIXKaiPGA=
github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070/go.mod h1:FqZi9WZ/Uzvs3tvo7l+OViThCpfvu3KDGDqHCB2LNbg=
github.com/grafana/cog v0.0.5 h1:BCa+10i3KvV+KMSQuxlN1DS9cZEwN+EAFc7ZmXqHxQE=
github.com/grafana/cog v0.0.5/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -73,20 +73,20 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY=
github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-1
View File
@@ -429,7 +429,6 @@
"react-split-pane@0.1.92": "patch:react-split-pane@npm:0.1.92#.yarn/patches/react-split-pane-npm-0.1.92-93dbf51dff.patch",
"history@4.10.1": "patch:history@npm%3A4.10.1#./.yarn/patches/history-npm-4.10.1-ee217563ae.patch",
"redux": "^5.0.0",
"@storybook/blocks@npm:8.1.6": "patch:@storybook/blocks@npm%3A8.1.6#~/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch",
"react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch",
"@grafana/plugin-e2e/@grafana/e2e-selectors": "workspace:*",
"@grafana/scenes/@grafana/e2e-selectors": "workspace:*",
@@ -160,6 +160,7 @@ export interface FeatureToggles {
onPremToCloudMigrationsAlerts?: boolean;
onPremToCloudMigrationsAuthApiMig?: boolean;
alertingSaveStatePeriodic?: boolean;
scopeApi?: boolean;
promQLScope?: boolean;
logQLScope?: boolean;
sqlExpressions?: boolean;
@@ -240,5 +241,6 @@ export interface FeatureToggles {
jaegerBackendMigration?: boolean;
reportingUseRawTimeRange?: boolean;
alertingUIOptimizeReducer?: boolean;
azureMonitorEnableUserAuth?: boolean;
alertingNotificationsStepMode?: boolean;
}
@@ -1,5 +1,8 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
import * as common from '@grafana/schema';
export interface DashboardV2Spec {
// Unique numeric identifier for the dashboard.
// `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances.
@@ -42,7 +45,7 @@ export interface DashboardV2Spec {
export const defaultDashboardV2Spec = (): DashboardV2Spec => ({
title: "",
cursorSync: DashboardCursorSync.Off,
cursorSync: "Off",
preload: false,
editable: true,
links: [],
@@ -69,13 +72,9 @@ export const defaultAnnotationPanelFilter = (): AnnotationPanelFilter => ({
// "Off" for no shared crosshair or tooltip (default).
// "Crosshair" for shared crosshair.
// "Tooltip" for shared crosshair AND shared tooltip.
export enum DashboardCursorSync {
Off = "Off",
Crosshair = "Crosshair",
Tooltip = "Tooltip",
}
export type DashboardCursorSync = "Off" | "Crosshair" | "Tooltip";
export const defaultDashboardCursorSync = (): DashboardCursorSync => (DashboardCursorSync.Off);
export const defaultDashboardCursorSync = (): DashboardCursorSync => ("Off");
// Links with references to other dashboards or external resources
export interface DashboardLink {
@@ -104,7 +103,7 @@ export interface DashboardLink {
export const defaultDashboardLink = (): DashboardLink => ({
title: "",
type: DashboardLinkType.Link,
type: "link",
icon: "",
tooltip: "",
tags: [],
@@ -135,8 +134,7 @@ export interface DataTransformerConfig {
// Optional frame matcher. When missing it will be applied to all results
filter?: MatcherConfig;
// Where to pull DataFrames from as input to transformation
// replaced with common.DataTopic
topic?: "series" | "annotations" | "alertStates";
topic?: common.DataTopic;
// Options to be passed to the transformer
// Valid options depend on the transformer id
options: any;
@@ -247,7 +245,7 @@ export const defaultMatcherConfig = (): MatcherConfig => ({
});
export interface Threshold {
value: number | null;
value: number;
color: string;
}
@@ -256,12 +254,9 @@ export const defaultThreshold = (): Threshold => ({
color: "",
});
export enum ThresholdsMode {
Absolute = "absolute",
Percentage = "percentage",
}
export type ThresholdsMode = "absolute" | "percentage";
export const defaultThresholdsMode = (): ThresholdsMode => (ThresholdsMode.Absolute);
export const defaultThresholdsMode = (): ThresholdsMode => ("absolute");
export interface ThresholdsConfig {
mode: ThresholdsMode;
@@ -269,7 +264,7 @@ export interface ThresholdsConfig {
}
export const defaultThresholdsConfig = (): ThresholdsConfig => ({
mode: ThresholdsMode.Absolute,
mode: "absolute",
steps: [],
});
@@ -282,14 +277,9 @@ export const defaultValueMapping = (): ValueMapping => (defaultValueMap());
// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.
export enum MappingType {
Value = "value",
Range = "range",
Regex = "regex",
Special = "special",
}
export type MappingType = "value" | "range" | "regex" | "special";
export const defaultMappingType = (): MappingType => (MappingType.Value);
export const defaultMappingType = (): MappingType => ("value");
// Maps text values to a color or different display text and color.
// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
@@ -365,22 +355,15 @@ export interface SpecialValueMap {
export const defaultSpecialValueMap = (): SpecialValueMap => ({
type: "special",
options: {
match: SpecialValueMatch.True,
match: "true",
result: defaultValueMappingResult(),
},
});
// Special value types supported by the `SpecialValueMap`
export enum SpecialValueMatch {
True = "true",
False = "false",
Null = "null",
NotANumber = "nan",
NullNan = "null+nan",
Empty = "empty",
}
export type SpecialValueMatch = "true" | "false" | "null" | "nan" | "null+nan" | "empty";
export const defaultSpecialValueMatch = (): SpecialValueMatch => (SpecialValueMatch.True);
export const defaultSpecialValueMatch = (): SpecialValueMatch => ("true");
// Result used as replacement with text and color when the value matches
export interface ValueMappingResult {
@@ -415,34 +398,14 @@ export const defaultValueMappingResult = (): ValueMappingResult => ({
// `continuous-purples`: Continuous Purple palette mode
// `shades`: Shades of a single color. Specify a single color, useful in an override rule.
// `fixed`: Fixed color mode. Specify a single color, useful in an override rule.
export enum FieldColorModeId {
Thresholds = "thresholds",
PaletteClassic = "palette-classic",
PaletteClassicByName = "palette-classic-by-name",
ContinuousGrYlRd = "continuous-GrYlRd",
ContinuousRdYlGr = "continuous-RdYlGr",
ContinuousBlYlRd = "continuous-BlYlRd",
ContinuousYlRd = "continuous-YlRd",
ContinuousBlPu = "continuous-BlPu",
ContinuousYlBl = "continuous-YlBl",
ContinuousBlues = "continuous-blues",
ContinuousReds = "continuous-reds",
ContinuousGreens = "continuous-greens",
ContinuousPurples = "continuous-purples",
Fixed = "fixed",
Shades = "shades",
}
export type FieldColorModeId = "thresholds" | "palette-classic" | "palette-classic-by-name" | "continuous-GrYlRd" | "continuous-RdYlGr" | "continuous-BlYlRd" | "continuous-YlRd" | "continuous-BlPu" | "continuous-YlBl" | "continuous-blues" | "continuous-reds" | "continuous-greens" | "continuous-purples" | "fixed" | "shades";
export const defaultFieldColorModeId = (): FieldColorModeId => (FieldColorModeId.Thresholds);
export const defaultFieldColorModeId = (): FieldColorModeId => ("thresholds");
// Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.
export enum FieldColorSeriesByMode {
Min = "min",
Max = "max",
Last = "last",
}
export type FieldColorSeriesByMode = "min" | "max" | "last";
export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => (FieldColorSeriesByMode.Min);
export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => ("min");
// Map a field to a color.
export interface FieldColor {
@@ -455,16 +418,13 @@ export interface FieldColor {
}
export const defaultFieldColor = (): FieldColor => ({
mode: FieldColorModeId.Thresholds,
mode: "thresholds",
});
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
export enum DashboardLinkType {
Link = "link",
Dashboards = "dashboards",
}
export type DashboardLinkType = "link" | "dashboards";
export const defaultDashboardLinkType = (): DashboardLinkType => (DashboardLinkType.Link);
export const defaultDashboardLinkType = (): DashboardLinkType => ("link");
// --- Common types ---
export interface Kind {
@@ -503,11 +463,9 @@ export const defaultVizConfigKind = (): VizConfigKind => ({
});
export interface AnnotationQuerySpec {
datasource: DataSourceRef;
datasource?: DataSourceRef;
query: DataQueryKind;
// TODO: Should be figured out based on datasource (Grafana ds)
// builtIn?: int
// Below are currently existing options for annotation queries
builtIn?: boolean;
enable: boolean;
filter: AnnotationPanelFilter;
hide: boolean;
@@ -516,7 +474,6 @@ export interface AnnotationQuerySpec {
}
export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({
datasource: defaultDataSourceRef(),
query: defaultDataQueryKind(),
enable: false,
filter: defaultAnnotationPanelFilter(),
@@ -542,6 +499,7 @@ export interface QueryOptionsSpec {
queryCachingTTL?: number;
interval?: string;
cacheTimeout?: string;
hideTimeOverride?: boolean;
}
export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({
@@ -560,14 +518,13 @@ export const defaultDataQueryKind = (): DataQueryKind => ({
export interface PanelQuerySpec {
query: DataQueryKind;
datasource: DataSourceRef;
datasource?: DataSourceRef;
refId: string;
hidden: boolean;
}
export const defaultPanelQuerySpec = (): PanelQuerySpec => ({
query: defaultDataQueryKind(),
datasource: defaultDataSourceRef(),
refId: "",
hidden: false,
});
@@ -782,7 +739,7 @@ export interface CustomFormatterVariable {
export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({
name: "",
type: VariableType.Query,
type: "query",
multi: false,
includeAll: false,
});
@@ -813,7 +770,7 @@ export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn =>
value: {},
legacyVariableModel: {
name: "",
type: VariableType.Query,
type: "query",
multi: false,
includeAll: false,
},
@@ -828,20 +785,9 @@ export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn =>
// `textbox`: Display a free text input field with an optional default value.
// `custom`: Define the variable options manually using a comma-separated list.
// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
export enum VariableType {
Query = "query",
Adhoc = "adhoc",
Groupby = "groupby",
Constant = "constant",
Datasource = "datasource",
Interval = "interval",
Textbox = "textbox",
Custom = "custom",
System = "system",
Snapshot = "snapshot",
}
export type VariableType = "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" | "snapshot";
export const defaultVariableType = (): VariableType => (VariableType.Query);
export const defaultVariableType = (): VariableType => ("query");
// Sort variable options
// Accepted values are:
@@ -855,41 +801,23 @@ export const defaultVariableType = (): VariableType => (VariableType.Query);
// `naturalAsc`: Natural ASC
// `naturalDesc`: Natural DESC
// VariableSort enum with default value
export enum VariableSort {
Disabled = "disabled",
AlphabeticalAsc = "alphabeticalAsc",
AlphabeticalDesc = "alphabeticalDesc",
NumericalAsc = "numericalAsc",
NumericalDesc = "numericalDesc",
AlphabeticalCaseInsensitiveAsc = "alphabeticalCaseInsensitiveAsc",
AlphabeticalCaseInsensitiveDesc = "alphabeticalCaseInsensitiveDesc",
NaturalAsc = "naturalAsc",
NaturalDesc = "naturalDesc",
}
export type VariableSort = "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc";
export const defaultVariableSort = (): VariableSort => (VariableSort.Disabled);
export const defaultVariableSort = (): VariableSort => ("disabled");
// Options to config when to refresh a variable
// `never`: Never refresh the variable
// `onDashboardLoad`: Queries the data source every time the dashboard loads.
// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes.
export enum VariableRefresh {
Never = "never",
OnDashboardLoad = "onDashboardLoad",
OnTimeRangeChanged = "onTimeRangeChanged",
}
export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged";
export const defaultVariableRefresh = (): VariableRefresh => (VariableRefresh.Never);
export const defaultVariableRefresh = (): VariableRefresh => ("never");
// Determine if the variable shows on dashboard
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
export enum VariableHide {
DontHide = "dontHide",
HideLabel = "hideLabel",
HideVariable = "hideVariable",
}
export type VariableHide = "dontHide" | "hideLabel" | "hideVariable";
export const defaultVariableHide = (): VariableHide => (VariableHide.DontHide);
export const defaultVariableHide = (): VariableHide => ("dontHide");
// FIXME: should we introduce this? --- Variable value option
export interface VariableValueOption {
@@ -927,7 +855,7 @@ export interface QueryVariableSpec {
refresh: VariableRefresh;
skipUrlSync: boolean;
description?: string;
datasource: DataSourceRef;
datasource?: DataSourceRef;
query: string | DataQueryKind;
regex: string;
sort: VariableSort;
@@ -942,13 +870,12 @@ export interface QueryVariableSpec {
export const defaultQueryVariableSpec = (): QueryVariableSpec => ({
name: "",
current: { text: "", value: "", },
hide: VariableHide.DontHide,
refresh: VariableRefresh.Never,
hide: "dontHide",
refresh: "never",
skipUrlSync: false,
datasource: defaultDataSourceRef(),
query: "",
regex: "",
sort: VariableSort.Disabled,
sort: "disabled",
options: [],
multi: false,
includeAll: false,
@@ -980,7 +907,7 @@ export const defaultTextVariableSpec = (): TextVariableSpec => ({
name: "",
current: { text: "", value: "", },
query: "",
hide: VariableHide.DontHide,
hide: "dontHide",
skipUrlSync: false,
});
@@ -1010,7 +937,7 @@ export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({
name: "",
query: "",
current: { text: "", value: "", },
hide: VariableHide.DontHide,
hide: "dontHide",
skipUrlSync: false,
});
@@ -1046,14 +973,14 @@ export interface DatasourceVariableSpec {
export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({
name: "",
pluginId: "",
refresh: VariableRefresh.Never,
refresh: "never",
regex: "",
current: { text: "", value: "", },
defaultOptionEnabled: false,
options: [],
multi: false,
includeAll: false,
hide: VariableHide.DontHide,
hide: "dontHide",
skipUrlSync: false,
});
@@ -1092,8 +1019,8 @@ export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({
auto: false,
auto_min: "",
auto_count: 0,
refresh: VariableRefresh.Never,
hide: VariableHide.DontHide,
refresh: "never",
hide: "dontHide",
skipUrlSync: false,
});
@@ -1130,7 +1057,7 @@ export const defaultCustomVariableSpec = (): CustomVariableSpec => ({
options: [],
multi: false,
includeAll: false,
hide: VariableHide.DontHide,
hide: "dontHide",
skipUrlSync: false,
});
@@ -1148,7 +1075,7 @@ export const defaultCustomVariableKind = (): CustomVariableKind => ({
// GroupBy variable specification
export interface GroupByVariableSpec {
name: string;
datasource: DataSourceRef;
datasource?: DataSourceRef;
current: VariableOption;
options: VariableOption[];
multi: boolean;
@@ -1162,12 +1089,11 @@ export interface GroupByVariableSpec {
export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({
name: "",
datasource: defaultDataSourceRef(),
current: { text: "", value: "", },
options: [],
multi: false,
includeAll: false,
hide: VariableHide.DontHide,
hide: "dontHide",
skipUrlSync: false,
});
@@ -1185,7 +1111,7 @@ export const defaultGroupByVariableKind = (): GroupByVariableKind => ({
// Adhoc variable specification
export interface AdhocVariableSpec {
name: string;
datasource: DataSourceRef;
datasource?: DataSourceRef;
baseFilters: AdHocFilterWithLabels[];
filters: AdHocFilterWithLabels[];
defaultKeys: MetricFindValue[];
@@ -1197,11 +1123,10 @@ export interface AdhocVariableSpec {
export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({
name: "",
datasource: defaultDataSourceRef(),
baseFilters: [],
filters: [],
defaultKeys: [],
hide: VariableHide.DontHide,
hide: "dontHide",
skipUrlSync: false,
});
@@ -1,4 +1,8 @@
package dashboard
package v2alpha0
import (
"github.com/grafana/grafana/packages/grafana-schema/src/common"
)
DashboardV2Spec: {
// Unique numeric identifier for the dashboard.
@@ -113,7 +117,7 @@ DataTransformerConfig: {
// Optional frame matcher. When missing it will be applied to all results
filter?: MatcherConfig
// Where to pull DataFrames from as input to transformation
topic?: "series" | "annotations" | "alertStates" // replaced with common.DataTopic
topic?: common.DataTopic
// Options to be passed to the transformer
// Valid options depend on the transformer id
options: _
@@ -217,7 +221,7 @@ MatcherConfig: {
}
Threshold: {
value: number | null
value: number
color: string
}
@@ -235,7 +239,7 @@ ValueMapping: ValueMap | RangeMap | RegexMap | SpecialValueMap
// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.
MappingType: "value" | "range" | "regex" | "special"
MappingType: "value" | "range" | "regex" | "special" @cog(kind="enum",memberNames="ValueToText|RangeToText|RegexToText|SpecialValue")
// Maps text values to a color or different display text and color.
// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
@@ -287,7 +291,7 @@ SpecialValueMap: {
}
// Special value types supported by the `SpecialValueMap`
SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty"
SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty" @cog(kind="enum",memberNames="True|False|Null|NaN|NullAndNaN|Empty")
// Result used as replacement with text and color when the value matches
ValueMappingResult: {
@@ -358,12 +362,9 @@ VizConfigKind: {
}
AnnotationQuerySpec: {
datasource: DataSourceRef
datasource?: DataSourceRef
query: DataQueryKind
// TODO: Should be figured out based on datasource (Grafana ds)
// builtIn?: int
// Below are currently existing options for annotation queries
builtIn?: bool
enable: bool
filter: AnnotationPanelFilter
hide: bool
@@ -383,6 +384,7 @@ QueryOptionsSpec: {
queryCachingTTL?: int
interval?: string
cacheTimeout?: string
hideTimeOverride?: bool
}
DataQueryKind: {
@@ -393,7 +395,7 @@ DataQueryKind: {
PanelQuerySpec: {
query: DataQueryKind
datasource: DataSourceRef
datasource?: DataSourceRef
refId: string
hidden: bool
@@ -593,7 +595,7 @@ QueryVariableSpec: {
refresh: VariableRefresh
skipUrlSync: bool | *false
description?: string
datasource: DataSourceRef | *{}
datasource?: DataSourceRef
query: string | DataQueryKind | *""
regex: string | *""
sort: VariableSort
@@ -727,7 +729,7 @@ CustomVariableKind: {
// GroupBy variable specification
GroupByVariableSpec: {
name: string | *""
datasource: DataSourceRef | *{}
datasource?: DataSourceRef
current: VariableOption | *{
text: ""
value: ""
@@ -751,7 +753,7 @@ GroupByVariableKind: {
// Adhoc variable specification
AdhocVariableSpec: {
name: string | *""
datasource: DataSourceRef | *{}
datasource?: DataSourceRef
baseFilters: [...AdHocFilterWithLabels] | *[]
filters: [...AdHocFilterWithLabels] | *[]
defaultKeys: [...MetricFindValue] | *[]
@@ -1,36 +1,32 @@
import { DashboardCursorSync, DashboardV2Spec } from './dashboard.gen';
import { DashboardV2Spec } from './dashboard.gen';
export const handyTestingSchema: DashboardV2Spec = {
id: 1,
title: 'Default Dashboard',
description: 'This is a default dashboard',
cursorSync: DashboardCursorSync.Off,
cursorSync: 'Off',
liveNow: false,
preload: false,
editable: true,
links: [],
tags: [],
schemaVersion: 39,
schemaVersion: 40,
tags: ['tag1', 'tag2'],
timeSettings: {
timezone: 'browser',
from: 'now-6h',
to: 'now',
autoRefresh: '10s',
autoRefreshIntervals: ['10s', '1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'],
quickRanges: ['now/d', 'now/w', 'now/M', 'now/y'],
hideTimepicker: false,
weekStart: 'sunday',
autoRefresh: '5s',
autoRefreshIntervals: ['5s', '10s', '30s'],
fiscalYearStartMonth: 1,
from: 'now-1h',
hideTimepicker: false,
nowDelay: '1m',
quickRanges: [],
timezone: 'UTC',
to: 'now',
weekStart: 'monday',
},
annotations: [],
elements: {
timeSeriesTest: {
'test-panel-uid': {
kind: 'Panel',
spec: {
title: 'Time Series Test',
description: 'This is a test panel',
uid: 'timeSeriesTest',
links: [],
data: {
kind: 'QueryGroup',
spec: {
@@ -38,44 +34,63 @@ export const handyTestingSchema: DashboardV2Spec = {
{
kind: 'PanelQuery',
spec: {
refId: 'A',
datasource: {
type: 'prometheus',
uid: 'datasource1',
},
query: {
kind: 'prometheus',
spec: {
query: 'up',
expr: 'test-query',
},
},
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
hidden: false,
refId: 'A',
},
},
],
queryOptions: {
timeFrom: '1h',
maxDataPoints: 100,
timeShift: '1h',
queryCachingTTL: 60,
interval: '1m',
cacheTimeout: '1m',
hideTimeOverride: false,
},
transformations: [
{
kind: 'limit',
spec: {
id: 'limit', // id is competing w/ kind
id: 'limit',
disabled: false,
filter: {
id: 'byValue',
options: {
reducer: 'sum',
},
},
options: {
limit: 10,
},
},
},
],
queryOptions: {
maxDataPoints: 100,
cacheTimeout: '1m',
},
},
},
description: 'Test Description',
links: [],
title: 'Test Panel',
uid: 'test-panel-uid',
vizConfig: {
kind: 'timeseries',
spec: {
pluginVersion: '11.0.0',
options: {},
fieldConfig: {
defaults: {},
overrides: [],
},
options: {},
pluginVersion: '7.0.0',
},
},
},
@@ -88,62 +103,252 @@ export const handyTestingSchema: DashboardV2Spec = {
{
kind: 'GridLayoutItem',
spec: {
element: { kind: 'ElementReference', name: 'timeSeriesTest' },
element: {
kind: 'ElementReference',
name: 'test-panel-uid',
},
height: 0,
width: 0,
x: 0,
y: 0,
width: 12,
height: 6,
},
},
],
},
},
variables: [],
annotations: [
links: [
{
kind: 'AnnotationQuery',
asDropdown: false,
icon: '',
includeVars: false,
keepTime: false,
tags: [],
targetBlank: false,
title: 'Test Link',
tooltip: '',
type: 'dashboards',
url: 'http://test.com',
},
],
variables: [
{
kind: 'QueryVariable',
spec: {
datasource: { type: 'datasource', uid: 'grafana' },
query: {
kind: 'grafana',
spec: {
queryType: 'timeRegions',
matchAny: false,
timeRegion: {
from: '12:27',
fromDayOfWeek: 2,
timezone: 'browser',
to: '11:30',
toDayOfWeek: 2,
},
},
allValue: '*',
current: {
text: 'text1',
value: 'value1',
},
enable: true,
filter: {
ids: [],
datasource: {
type: 'prometheus',
uid: 'datasource1',
},
hide: false,
iconColor: 'blue',
name: 'Grafana annotations',
definition: 'definition1',
description: 'A query variable',
hide: 'dontHide',
includeAll: true,
label: 'Query Variable',
multi: true,
name: 'queryVar',
options: [],
query: 'query1',
refresh: 'onDashboardLoad',
regex: 'regex1',
skipUrlSync: false,
sort: 'disabled',
},
},
{
kind: 'AnnotationQuery',
kind: 'CustomVariable',
spec: {
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
query: {
kind: 'prometheus',
spec: {
query: 'up',
allValue: 'All',
current: {
text: 'option1',
value: 'option1',
},
description: 'A custom variable',
hide: 'dontHide',
includeAll: true,
label: 'Custom Variable',
multi: true,
name: 'customVar',
options: [
{
selected: true,
text: 'option1',
value: 'option1',
},
{
selected: false,
text: 'option2',
value: 'option2',
},
],
query: 'option1, option2',
skipUrlSync: false,
},
},
{
kind: 'DatasourceVariable',
spec: {
allValue: undefined,
current: {
text: 'text1',
value: 'value1',
},
enable: true,
filter: {
ids: [],
defaultOptionEnabled: true,
description: 'A datasource variable',
hide: 'dontHide',
includeAll: false,
label: 'Datasource Variable',
multi: false,
name: 'datasourceVar',
options: [],
pluginId: 'datasource1',
refresh: 'onDashboardLoad',
regex: 'regex1',
skipUrlSync: false,
},
},
{
kind: 'ConstantVariable',
spec: {
current: {
text: 'value4',
value: 'value4',
},
hide: false,
iconColor: 'red',
name: 'Prometheus annotations',
description: 'A constant variable',
hide: 'dontHide',
label: 'Constant Variable',
name: 'constantVar',
query: 'value4',
skipUrlSync: true,
},
},
{
kind: 'IntervalVariable',
spec: {
auto: false,
auto_count: 10,
auto_min: '1m',
current: {
text: '1m',
value: '1m',
},
description: 'An interval variable',
hide: 'dontHide',
label: 'Interval Variable',
name: 'intervalVar',
options: [
{
selected: true,
text: '1m',
value: '1m',
},
{
selected: false,
text: '5m',
value: '5m',
},
{
selected: false,
text: '10m',
value: '10m',
},
],
query: '1m,5m,10m',
refresh: 'onDashboardLoad',
skipUrlSync: false,
},
},
{
kind: 'TextVariable',
spec: {
current: {
text: 'value6',
value: 'value6',
},
description: 'A text variable',
hide: 'dontHide',
label: 'Text Variable',
name: 'textVar',
query: 'value6',
skipUrlSync: false,
},
},
{
kind: 'GroupByVariable',
spec: {
current: {
text: 'text7',
value: 'value7',
},
datasource: {
type: 'prometheus',
uid: 'datasource2',
},
description: 'A group by variable',
hide: 'dontHide',
includeAll: false,
label: 'Group By Variable',
multi: false,
name: 'groupByVar',
options: [
{
text: 'option1',
value: 'option1',
},
{
text: 'option2',
value: 'option2',
},
],
skipUrlSync: false,
},
},
{
kind: 'AdhocVariable',
spec: {
baseFilters: [
{
condition: 'AND',
key: 'key1',
operator: '=',
value: 'value1',
},
{
condition: 'OR',
key: 'key2',
operator: '=',
value: 'value2',
},
],
datasource: {
type: 'prometheus',
uid: 'datasource3',
},
defaultKeys: [
{
expandable: true,
group: 'defaultGroup1',
text: 'defaultKey1',
value: 'defaultKey1',
},
],
description: 'An adhoc variable',
filters: [
{
condition: 'AND',
key: 'key3',
operator: '=',
value: 'value3',
},
],
hide: 'dontHide',
label: 'Adhoc Variable',
name: 'adhocVar',
skipUrlSync: false,
},
},
],
@@ -0,0 +1,62 @@
// This script is used to copy assets from the public folder to a temporary static folder within
// the .storybook directory.
// We selectively limit assets that are uploaded to the Storybook bucket to prevent rate limiting
// when publishing new storybook.
// Note: Storybook has a static copying feature but it copies entire directories which can contain thousands of icons.
import { copySync, emptyDirSync, lstatSync } from 'fs-extra';
import { resolve } from 'node:path';
// avoid importing from @grafana/data to prevent error: window is not defined
import { availableIconsIndex, IconName } from '../../grafana-data/src/types/icon';
import { getIconSubDir } from '../src/components/Icon/utils';
// doesn't require uploading 1000s of unused assets.
const iconPaths = Object.keys(availableIconsIndex)
.filter((iconName) => !iconName.startsWith('fa '))
.map((iconName) => {
const subDir = getIconSubDir(iconName as IconName, 'default');
return {
from: `../../../public/img/icons/${subDir}/${iconName}.svg`,
to: `./static/public/img/icons/${subDir}/${iconName}.svg`,
};
});
export function copyAssetsSync() {
const assets = [
{
from: '../../../public/fonts',
to: './static/public/fonts',
},
{
from: '../../../public/img/grafana_text_logo-dark.svg',
to: './static/public/img/grafana_text_logo-dark.svg',
},
{
from: '../../../public/img/grafana_text_logo-light.svg',
to: './static/public/img/grafana_text_logo-light.svg',
},
{
from: '../../../public/img/fav32.png',
to: './static/public/img/fav32.png',
},
{
from: '../../../public/lib',
to: './static/public/lib',
},
...iconPaths,
];
const staticDir = resolve(__dirname, 'static', 'public');
emptyDirSync(staticDir);
for (const asset of assets) {
const fromPath = resolve(__dirname, asset.from);
const toPath = resolve(__dirname, asset.to);
copySync(fromPath, toPath, {
filter: (src) => !lstatSync(src).isSymbolicLink(),
});
}
}
+11 -43
View File
@@ -1,8 +1,6 @@
import path, { dirname, join } from 'path';
import path, { dirname, join } from 'node:path';
import type { StorybookConfig } from '@storybook/react-webpack5';
// avoid importing from @grafana/data to prevent node error: ERR_REQUIRE_ESM
import { availableIconsIndex, IconName } from '../../grafana-data/src/types/icon';
import { getIconSubDir } from '../src/components/Icon/utils';
import { copyAssetsSync } from './copyAssets';
// Internal stories should only be visible during development
const storyGlob =
@@ -12,17 +10,8 @@ const storyGlob =
const stories = ['../src/Intro.mdx', storyGlob];
// We limit icon paths to only the available icons so publishing
// doesn't require uploading 1000s of unused assets.
const iconPaths = Object.keys(availableIconsIndex)
.filter((iconName) => !iconName.startsWith('fa '))
.map((iconName) => {
const subDir = getIconSubDir(iconName as IconName, 'default');
return {
from: `../../../public/img/icons/${subDir}/${iconName}.svg`,
to: `/public/img/icons/${subDir}/${iconName}.svg`,
};
});
// Copy the assets required by storybook before starting the storybook server.
copyAssetsSync();
const mainConfig: StorybookConfig = {
stories,
@@ -45,17 +34,18 @@ const mainConfig: StorybookConfig = {
url: false,
importLoaders: 2,
},
sassLoaderOptions: {
sassOptions: {
// silencing these warnings since we're planning to remove sass when angular is gone
silenceDeprecations: ['import', 'global-builtin'],
},
},
},
},
getAbsolutePath('@storybook/addon-storysource'),
getAbsolutePath('storybook-dark-mode'),
getAbsolutePath('@storybook/addon-mdx-gfm'),
getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
],
core: {},
docs: {
autodocs: true,
},
framework: {
name: getAbsolutePath('@storybook/react-webpack5'),
options: {
@@ -66,29 +56,7 @@ const mainConfig: StorybookConfig = {
},
},
logLevel: 'debug',
staticDirs: [
{
from: '../../../public/fonts',
to: '/public/fonts',
},
{
from: '../../../public/img/grafana_text_logo-dark.svg',
to: '/public/img/grafana_text_logo-dark.svg',
},
{
from: '../../../public/img/grafana_text_logo-light.svg',
to: '/public/img/grafana_text_logo-light.svg',
},
{
from: '../../../public/img/fav32.png',
to: '/public/img/fav32.png',
},
{
from: '../../../public/lib',
to: '/public/lib',
},
...iconPaths,
],
staticDirs: ['static'],
typescript: {
check: true,
reactDocgen: 'react-docgen-typescript',
+1 -1
View File
@@ -46,7 +46,6 @@ const preview: Preview = {
knobs: {
disable: true,
},
layout: 'fullscreen',
options: {
// Sort stories first by Docs Overview, then alphabetically
// We should be able to use the builtin alphabetical sort, but is broken in SB 7.0
@@ -83,6 +82,7 @@ const preview: Preview = {
},
},
},
tags: ['autodocs'],
};
export default preview;
+17 -16
View File
@@ -118,22 +118,22 @@
"@faker-js/faker": "^9.0.0",
"@grafana/tsconfig": "^2.0.0",
"@rollup/plugin-node-resolve": "15.3.0",
"@storybook/addon-a11y": "^8.1.6",
"@storybook/addon-actions": "^8.1.6",
"@storybook/addon-docs": "^8.1.6",
"@storybook/addon-essentials": "^8.1.6",
"@storybook/addon-mdx-gfm": "^8.1.6",
"@storybook/addon-storysource": "^8.1.6",
"@storybook/addon-webpack5-compiler-swc": "^1.0.2",
"@storybook/blocks": "patch:@storybook/blocks@npm%3A8.1.6#~/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch",
"@storybook/components": "^8.1.6",
"@storybook/core-events": "^8.1.6",
"@storybook/addon-a11y": "^8.4.2",
"@storybook/addon-actions": "^8.4.2",
"@storybook/addon-docs": "^8.4.2",
"@storybook/addon-essentials": "^8.4.2",
"@storybook/addon-storysource": "^8.4.2",
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
"@storybook/blocks": "^8.4.2",
"@storybook/components": "^8.4.2",
"@storybook/core-events": "^8.4.2",
"@storybook/manager-api": "^8.4.2",
"@storybook/mdx2-csf": "1.1.0",
"@storybook/preset-scss": "1.0.3",
"@storybook/preview-api": "^8.1.6",
"@storybook/react": "^8.1.6",
"@storybook/react-webpack5": "^8.1.6",
"@storybook/theming": "^8.1.6",
"@storybook/preview-api": "^8.4.2",
"@storybook/react": "^8.4.2",
"@storybook/react-webpack5": "^8.4.2",
"@storybook/theming": "^8.4.2",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1",
@@ -166,6 +166,7 @@
"csstype": "3.1.3",
"esbuild": "0.24.0",
"expose-loader": "5.0.0",
"fs-extra": "^11.2.0",
"mock-raf": "1.0.1",
"process": "^0.11.10",
"react": "18.2.0",
@@ -180,8 +181,8 @@
"rollup-plugin-node-externals": "^7.1.3",
"rollup-plugin-svg-import": "3.0.0",
"sass-loader": "16.0.3",
"storybook": "^8.1.6",
"storybook-dark-mode": "^4.0.1",
"storybook": "^8.4.2",
"storybook-dark-mode": "4.0.1",
"style-loader": "4.0.0",
"typescript": "5.5.4",
"webpack": "5.95.0"
@@ -1,6 +1,6 @@
import { Preview, ArgTypes } from '@storybook/blocks';
import { Canvas, ArgTypes } from '@storybook/blocks';
import { AutoSaveField } from './AutoSaveField';
import { Basic } from './AutoSaveField.story.tsx';
import * as AutoSaveFieldStories from './AutoSaveField.story';
# AutoSaveField
@@ -217,9 +217,7 @@ The `AutoSaveField`component is comprised of:
{' '}
<Preview>
<Basic />
</Preview>
<Canvas of={AutoSaveFieldStories.Basic} />
<br />
### Placement
@@ -1,15 +1,12 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes, Unstyled } from '@storybook/blocks';
import { Button, LinkButton } from './Button';
import { Alert } from '../Alert/Alert';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|Button" component={Button} />
# Button
<Alert severity="warning" title={'Please note:'}>
After reviewing this component we are asking you to use the IconButton when you require a button with only an icon.
</Alert>
When using a button please always follow a11y rules (e.g. W3C Recommendation [3.3.2 Labels or instructions](https://www.w3.org/TR/WCAG21/#labels-or-instructions)) and make sure the context in which the button is located is also communicated by screen readers.
## Primary
@@ -18,19 +15,17 @@ Used for "call to action", i.e. triggering the main action. There should never b
If there is no primary action, all `Button` components should be secondary.
<Preview>
<div>
<Button variant="primary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="primary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="primary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ExampleFrame>
<Button variant="primary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="primary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="primary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Secondary
@@ -39,70 +34,64 @@ The secondary `Button` is the default button style and can trigger various actio
1. When next to the primary `Button`, the Secondary style can for example be used for "Cancel" or "Abort" actions.
2. When there is no main important action on a given page, all `Button` components should use the secondary style.
<Preview>
<div>
<Button variant="secondary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="secondary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="secondary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ExampleFrame>
<Button variant="secondary" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="secondary" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="secondary" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Destructive
Used for triggering a removing or deleting action. Because of its dominant coloring, it should be used sparingly. If you need multiple Destructive `Button` components in one view, we recommend using a secondary `Button` or Link variant instead and only use the Destructive variant to double confirm.
<Preview>
<div>
<Button variant="destructive" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="destructive" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="destructive" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ExampleFrame>
<Button variant="destructive" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button variant="destructive" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button variant="destructive" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Text
<Preview>
<div>
<Button fill="text" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button fill="text" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button fill="text" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</div>
</Preview>
<ArgTypes of={Button} />
<ExampleFrame>
<Button fill="text" size="sm" style={{ margin: '5px' }}>
{'Small'}
</Button>
<Button fill="text" size="md" style={{ margin: '5px' }}>
{'Medium'}
</Button>
<Button fill="text" size="lg" style={{ margin: '5px' }}>
{'Large'}
</Button>
</ExampleFrame>
## Links
To add an anchor that looks like a button use the `<LinkButton>` component and pass a href prop.
<Preview>
<div>
<LinkButton href="/" size="sm" style={{ margin: '5px' }}>
{'Small'}
</LinkButton>
<LinkButton href="/" size="md" style={{ margin: '5px' }}>
{'Medium'}
</LinkButton>
<LinkButton href="/" size="lg" style={{ margin: '5px' }}>
{'Large'}
</LinkButton>
</div>
</Preview>
<ExampleFrame>
<LinkButton href="/" size="sm" style={{ margin: '5px' }}>
{'Small'}
</LinkButton>
<LinkButton href="/" size="md" style={{ margin: '5px' }}>
{'Medium'}
</LinkButton>
<LinkButton href="/" size="lg" style={{ margin: '5px' }}>
{'Large'}
</LinkButton>
</ExampleFrame>
## Props
<ArgTypes of={Button} />
@@ -1,16 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react';
import { MultiCombobox } from './MultiCombobox';
import mdx from './MultiCombobox.mdx';
const meta: Meta<typeof MultiCombobox> = {
title: 'Forms/MultiCombobox',
component: MultiCombobox,
parameters: {
docs: {
page: mdx,
},
},
};
export default meta;
@@ -0,0 +1,52 @@
import React, { createContext, useCallback, useContext } from 'react';
/** @alpha */
export interface ElementSelectionContextState {
/**
* Turn on selection mode & show selection state
*/
enabled?: boolean;
/** List of currently selected elements */
selected: ElementSelectionContextItem[];
onSelect: (item: ElementSelectionContextItem, multi?: boolean) => void;
}
export interface ElementSelectionContextItem {
id: string;
}
export const ElementSelectionContext = createContext<ElementSelectionContextState | undefined>(undefined);
export interface UseElementSelectionResult {
isSelected?: boolean;
isSelectable?: boolean;
onSelect?: (evt: React.PointerEvent) => void;
}
export function useElementSelection(id: string | undefined): UseElementSelectionResult {
if (!id) {
return {};
}
const context = useContext(ElementSelectionContext);
if (!context) {
return {};
}
const isSelected = context.selected.some((item) => item.id === id);
const onSelect = useCallback<React.PointerEventHandler>(
(evt) => {
if (!context.enabled) {
return;
}
// To prevent this click form clearing the selection
evt.stopPropagation();
context.onSelect({ id }, evt.shiftKey);
},
[context, id]
);
return { isSelected, onSelect, isSelectable: context.enabled };
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 93 KiB

@@ -1,7 +1,8 @@
import { ArgTypes, Preview } from '@storybook/blocks';
import { ArgTypes } from '@storybook/blocks';
import { AutoSizeInput } from './AutoSizeInput';
import { Field } from '../Forms/Field';
import { Icon } from '../Icon/Icon';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
# AutoSizeInput
@@ -15,9 +16,9 @@ To add more context to the input, you can add either text or an icon before or a
<AutoSizeInput prefix={<Icon name="search" />} />
```
<Preview>
<ExampleFrame>
<AutoSizeInput prefix={<Icon name="search" />} />
</Preview>
</ExampleFrame>
## Usage in forms with Field
@@ -29,9 +30,12 @@ Use `AutoSizeInput`with the`Field`component to get labels and descriptions. Also
</Field>
```
<Preview>
<ExampleFrame>
<Field label="Important information!" description="Please enter the relevant information.">
<AutoSizeInput name="importantInput" required />
</Field>
</Preview>
</ExampleFrame>
## Props
<ArgTypes of={AutoSizeInput} />
@@ -1,12 +1,16 @@
import { ArgTypes, Preview } from '@storybook/blocks';
import { ArgTypes, Canvas } from '@storybook/blocks';
import { Input } from './Input';
import { Field } from '../Forms/Field';
import { Icon } from '../Icon/Icon';
import * as InputStories from './Input.story';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
# Input
Used for regular text input. For an array of data or tree-structured data, consider using `Select` or `Cascader` respectively.
<Canvas of={InputStories.Simple} />
## Prefix and suffix
To add more context to the input you can add either text or an icon before or after the input. You can use the `prefix` and `suffix` props for this. Try some examples in the Preview!
@@ -15,9 +19,9 @@ To add more context to the input you can add either text or an icon before or af
<Input prefix={<Icon name="search" />} />
```
<Preview>
<ExampleFrame>
<Input prefix={<Icon name="search" />} />
</Preview>
</ExampleFrame>
## Usage in forms with Field
@@ -29,12 +33,15 @@ To add more context to the input you can add either text or an icon before or af
</Field>
```
<Preview>
<ExampleFrame>
<Field
label="Important information"
description="This information is very important, so you really need to fill it in"
>
<Input name="importantInput" required />
</Field>
</Preview>
</ExampleFrame>
## Props
<ArgTypes of={Input} />
@@ -1,7 +1,9 @@
import { Preview, ArgTypes } from '@storybook/blocks';
import { Canvas, ArgTypes } from '@storybook/blocks';
import { Layout, HorizontalGroup, VerticalGroup } from './Layout';
import { Button } from '../Button';
import { Select } from '../index';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
import * as LayoutStories from './Layout.story';
# Layout
@@ -19,78 +21,10 @@ Expects multiple elements as `children` prop. This is the base for more speciali
Used for horizontally aligning several elements (e.g. Button, Select) with a predefined spacing between them.
<Preview>
<HorizontalGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</HorizontalGroup>
</Preview>
<Preview>
<HorizontalGroup>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
</HorizontalGroup>
</Preview>
<Canvas of={LayoutStories.Horizontal} />
## Vertical
Used for vertically aligning several elements (e.g. Button, Select) with a predefined spacing between them.
<Preview>
<VerticalGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</VerticalGroup>
</Preview>
<Preview>
<VerticalGroup justify="center">
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
<Select
width={25}
onChange={() => {}}
options={[
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
]}
/>
</VerticalGroup>
</Preview>
<Canvas of={LayoutStories.Vertical} />
@@ -62,8 +62,9 @@ export default meta;
export const Horizontal: StoryFn<LayoutProps> = (args) => {
return (
<HorizontalGroup {...args}>
<Button variant="secondary">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button>Save</Button>
<Button>Cancel</Button>
</HorizontalGroup>
);
};
@@ -71,8 +72,9 @@ export const Horizontal: StoryFn<LayoutProps> = (args) => {
export const Vertical: StoryFn<LayoutProps> = (args) => {
return (
<VerticalGroup {...args}>
<Button variant="secondary">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button>Save</Button>
<Button>Cancel</Button>
</VerticalGroup>
);
};
@@ -1,6 +1,7 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { TextLink } from './TextLink';
import { Basic } from './TextLink.story.tsx';
import * as TextLinkStories from './TextLink.story.tsx';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|TextLink" component={TextLink} />
@@ -81,30 +82,26 @@ The following is the default behaviour and so, it will be applied according to i
### <a name="inline-behaviour"/>**Inline**:<br/>
<Preview>
<TextLink href="https://google.es" external inline>
{'This an inline link example'}
</TextLink>
</Preview>
- _Initial appearence_: Blue and underlined at first. - _On hover_: blue and not underlined
- _Initial appearence_: Blue and underlined at first.
- _On hover_: blue and not underlined
<Canvas of={TextLinkStories.Inline} />
### <a name="standalone-behaviour"/>**Standalone**:
<Preview>
<TextLink href="https://google.es" inline={false} external>
{'This an standalone link example'}
</TextLink>
</Preview>
- _Initial appearence_: Blue and not underlined at first. - _On hover_: blue and underlined.
- _Initial appearence_: Blue and not underlined at first.
- _On hover_: blue and underlined.
<Canvas of={TextLinkStories.Standalone} />
_Example_: We should take into account that through the color prop the user will be able to change this value,
but the rest will be kept. So, in this example, if the user renders an external and standalone _text link_ setting the color value to 'primary' the result will be a text link 'white' in dark theme or 'black' in light theme, as set by the user, and not underlined at first while blue and underlined when onhover, as it inherits from the standalone default behaviour.
<Preview>
<ExampleFrame>
<TextLink href="https://google.es" color="primary" inline={false} external>
{'This an external standalone link example'}
</TextLink>
</Preview>
</ExampleFrame>
```jsx
<TextLink href="https://google.es" color="primary" inline={false} external>
@@ -116,11 +113,11 @@ but the rest will be kept. So, in this example, if the user renders an external
By default, **external _text links_** will show an `external-alt-icon`. If by design reasons, the user needs to set another icon it can be done through the correspondent prop.
<Preview>
<ExampleFrame>
<TextLink href="https://google.es" external>
{'This an external with the default icon'}
</TextLink>
</Preview>
</ExampleFrame>
```jsx
<TextLink href="https://google.es" external>
@@ -130,11 +127,11 @@ By default, **external _text links_** will show an `external-alt-icon`. If by de
Both, **external** and **internal** links can show a specific icon using the `icon` prop.
<Preview>
<ExampleFrame>
<TextLink href="https://google.es" icon="google" external>
{'This an external with a specific icon'}
</TextLink>
</Preview>
</ExampleFrame>
```jsx
<TextLink href="https://google.es" icon="google" external>
This an external with a specific icon
@@ -32,7 +32,7 @@ const meta: Meta = {
inline: { control: 'boolean' },
},
args: {
href: 'https://www.google.com',
href: 'https://www.grafana.com',
external: true,
icon: 'external-link-alt',
},
@@ -67,14 +67,33 @@ Example.parameters = {
controls: { exclude: ['href', 'external', 'variant', 'weight', 'color', 'inline', 'icon'] },
};
export const Basic: StoryFn = (args) => {
export const Inline: StoryFn = (args) => {
return (
<div>
For more information{' '}
<TextLink href={args.href} {...args}>
Go to Google
see Grafana.com
</TextLink>
</div>
);
};
Inline.args = {
inline: true,
};
export const Standalone: StoryFn = (args) => {
return (
<div>
<TextLink href={args.href} {...args}>
Go to Grafana.com
</TextLink>
</div>
);
};
Standalone.args = {
inline: false,
};
export default meta;
@@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { LoadingBar } from './LoadingBar';
import * as LoadingBarStories from './LoadingBar.story';
<Meta title="MDX|LoadingBar" component={LoadingBar} />
@@ -7,16 +8,6 @@ import { LoadingBar } from './LoadingBar';
The LoadingBar is used as a simple loading slider animation in the top of its container.
<Preview>
<div style={{ height: '200px', width: '400px' }}>
<LoadingBar containerWidth={400} height={2} width={128} />
</div>
</Preview>
```jsx
<div style={{ height: '200px', width: '400px' }}>
<LoadingBar containerWidth={400} height={2} width={128} />
</div>
```
<Canvas of={LoadingBarStories.Basic} />
<ArgTypes of={LoadingBar} />
@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { Meta, Preview } from '@storybook/blocks';
import { Meta, Canvas } from '@storybook/blocks';
import { PanelChrome } from './PanelChrome';
import { action } from '@storybook/addon-actions';
@@ -8,6 +8,8 @@ import { Button } from '../Button';
import { Menu } from '../Menu/Menu';
import { Stack } from '../Layout/Stack/Stack';
import { IconButton } from '../IconButton/IconButton';
import * as PanelChromeStories from './PanelChrome.story';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|PanelChrome" component={PanelChrome} />
@@ -17,97 +19,11 @@ Component used for rendering content wrapped in the same style as grafana panels
### Basic Usage: Title, Description and Content
```tsx
<PanelChrome
title="My awesome panel title"
description="Here I will put a description that explains a bit more this panel"
width={400}
height={200}
>
{(innerwidth, innerheight) => {
return (
<div
style={{
width: innerwidth,
height: innerheight,
background: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
New panel with old API
</div>
);
}}
</PanelChrome>
```
<Preview>
<PanelChrome
title="My awesome panel title"
description="Here I will put a description that explains a bit more this panel"
width={400}
height={200}
>
{(innerwidth, innerheight) => {
return (
<div
style={{
width: innerwidth,
height: innerheight,
background: 'rgba(230,0,0,0.05)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
Content
</div>
);
}}
</PanelChrome>
</Preview>
<Canvas of={PanelChromeStories.Basic} />
### Menu: Standard & Hover
```tsx
<PanelChrome
title="My awesome panel title"
hoverHeader={<true || false>}
menu={() => (
<Menu>
<Menu.Item label="View" icon="eye" />
<Menu.Item label="Edit" icon="edit" />
<Menu.Item label="Share" icon="share-alt" />
<Menu.Divider />
<Menu.Item label="Remove" icon="trash-alt" />
</Menu>
)}
description="Here I will put a description that explains a bit more this panel"
width={400}
height={200}
>
{(innerwidth, innerheight) => {
return (
<div
style={{
width: innerwidth,
height: innerheight,
background: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
New panel with old API
</div>
);
}}
</PanelChrome>
```
<Preview>
<ExampleFrame>
<Stack gap={2} alignItems="flex-start" wrap="wrap">
<PanelChrome
@@ -183,7 +99,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
</Stack>
</Preview>
</ExampleFrame>
### States: Loading , Streaming, Error
@@ -215,7 +131,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<Stack gap={2} alignItems="flex-start" wrap="wrap">
<PanelChrome
@@ -298,7 +214,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
</Stack>
</Preview>
</ExampleFrame>
### Extra options? Title items and actions
@@ -345,7 +261,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<PanelChrome
title="My awesome panel title"
titleItems={
@@ -386,7 +302,7 @@ Component used for rendering content wrapped in the same style as grafana panels
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
### Migration from old PanelChrome (before v9.4.0)
@@ -418,7 +334,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<PanelChrome
title="My awesome panel title"
leftItems={[
@@ -448,7 +364,7 @@ Component used for rendering content wrapped in the same style as grafana panels
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
#### After
```tsx
@@ -478,7 +394,7 @@ Component used for rendering content wrapped in the same style as grafana panels
</PanelChrome>
```
<Preview>
<ExampleFrame>
<PanelChrome
title="My awesome panel title"
loadingState={LoadingState.Loading}
@@ -503,7 +419,7 @@ Component used for rendering content wrapped in the same style as grafana panels
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
### Collapsible
@@ -545,7 +461,7 @@ function Container() {
}
```
<Preview>
<ExampleFrame>
<PanelChrome title="My awesome panel title" width={400} height={200} collapsible={true}>
{(innerwidth, innerheight) => {
return (
@@ -564,4 +480,4 @@ function Container() {
);
}}
</PanelChrome>
</Preview>
</ExampleFrame>
@@ -9,6 +9,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { useStyles2, useTheme2 } from '../../themes';
import { getFocusStyles } from '../../themes/mixins';
import { DelayRender } from '../../utils/DelayRender';
import { useElementSelection } from '../ElementSelectionContext/ElementSelectionContext';
import { Icon } from '../Icon/Icon';
import { LoadingBar } from '../LoadingBar/LoadingBar';
import { Text } from '../Text/Text';
@@ -33,6 +34,7 @@ interface BaseProps {
menu?: ReactElement | (() => ReactElement);
dragClass?: string;
dragClassCancel?: string;
selectionId?: string;
/**
* Use only to indicate loading or streaming data in the panel.
* Any other values of loadingState are ignored.
@@ -131,6 +133,7 @@ export function PanelChrome({
statusMessageOnClick,
leftItems,
actions,
selectionId,
onCancelQuery,
onOpenMenu,
collapsible = false,
@@ -145,6 +148,7 @@ export function PanelChrome({
const styles = useStyles2(getStyles);
const panelContentId = useId();
const panelTitleId = useId().replace(/:/g, '_');
const { isSelected, onSelect } = useElementSelection(selectionId);
const hasHeader = !hoverHeader;
@@ -263,7 +267,11 @@ export function PanelChrome({
return (
// tabIndex={0} is needed for keyboard accessibility in the plot area
<section
className={cx(styles.container, { [styles.transparentContainer]: isPanelTransparent })}
className={cx(
styles.container,
isPanelTransparent && styles.transparentContainer,
isSelected && 'dashboard-selected-element'
)}
style={containerStyles}
aria-labelledby={!!title ? panelTitleId : undefined}
data-testid={testid}
@@ -300,7 +308,12 @@ export function PanelChrome({
)}
{hasHeader && (
<div className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container">
<div
className={cx(styles.headerContainer, dragClass)}
style={headerStyles}
data-testid="header-container"
onPointerUp={onSelect}
>
{statusMessage && (
<div className={dragClassCancel}>
<PanelStatus message={statusMessage} onClick={statusMessageOnClick} ariaLabel="Panel status" />
@@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { PluginSignatureBadge } from './PluginSignatureBadge';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|PluginSignatureBadge" component={PluginSignatureBadge} />
@@ -12,32 +13,32 @@ This is a security measure to make sure plugins haven't been tampered with. Upon
1.- Core: this badge indicates that the plugin has been built into Grafana.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="internal" />
</Preview>
</ExampleFrame>
2.- Signed: the plugin's signature was successfully verified.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="valid" />
</Preview>
</ExampleFrame>
3.- Invalid signature: this plugin has a invalid signature.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="invalid" />
</Preview>
</ExampleFrame>
4.- Modified signature: this plugin has changed since it was signed. This may indicate malicious intent.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="modified" />
</Preview>
</ExampleFrame>
5.- Unsigned: the plugin is not signed. In this case, as upon loading Grafana checks the plugins signature, Grafana will not load or start it. Furthermore, it will write an error message to the server log.
<Preview>
<ExampleFrame>
<PluginSignatureBadge status="unsigned" />
</Preview>
</ExampleFrame>
<ArgTypes of={PluginSignatureBadge} />
@@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { RefreshPicker } from './RefreshPicker';
import * as RefreshPickerStories from './RefreshPicker.story';
<Meta title="MDX|RefreshPicker" component={RefreshPicker} />
@@ -11,10 +12,6 @@ This component is used on dashboards to refresh visualizations. Grafana does not
**The down arrow:** will display a list of refresh intervals. If one of them is selected the dashboard will regularly refresh according to that schedule.
<Preview>
<RefreshPicker tooltip="Run query" />
<RefreshPicker text="Run query" tooltip="Run query" />
<RefreshPicker text="Run query" isLoading={true} tooltip="Run query" value="1h" />
</Preview>
<Canvas of={RefreshPickerStories.Examples} />
<ArgTypes of={RefreshPicker} />
@@ -1,4 +1,4 @@
import { ArgTypes } from '@storybook/blocks';
import { Meta, ArgTypes } from '@storybook/blocks';
import { RenderUserContentAsHTML } from './RenderUserContentAsHTML';
<Meta title="MDX|RenderUserContentAsHTML" component={RenderUserContentAsHTML} />
@@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { Spinner } from './Spinner';
import * as SpinnerStories from './Spinner.story';
<Meta title="MDX|Spinner" component={Spinner} />
@@ -7,11 +8,6 @@ import { Spinner } from './Spinner';
Spinner is `fa-spinner` icon animated. It is used to alert a user to wait for an activity to complete.
<Preview>
<div>
<Spinner />
<Spinner className="my-spin-div" iconClassName="my-spinner-classname" size={24} />
</div>
</Preview>
<Canvas of={SpinnerStories.Basic} />
<ArgTypes of={Spinner} />
@@ -1,5 +1,6 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { Tag } from './Tag';
import * as TagStories from './Tag.story';
<Meta title="MDX|Tag" component={Tag} />
@@ -7,19 +8,7 @@ import { Tag } from './Tag';
Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name.
<Preview>
<div>
<Tag name="Tag" onClick={(name) => console.log(name)} />
</div>
</Preview>
### Usage
```jsx
import { Tag } from '@grafana/ui';
<Tag name="Tag" onClick={(name) => console.log(name)} />;
```
<Canvas of={TagStories.Single} />
### Props
@@ -1,22 +1,12 @@
import { Preview, ArgTypes } from '@storybook/blocks';
import { Canvas, ArgTypes } from '@storybook/blocks';
import { TagList } from './TagList';
import * as TagListStories from './TagList.story';
# TagList
List of tags with predefined margins and positioning.
<Preview>
<TagList tags={['datasource-test', 'gdev', 'mysql', 'mssql']} />
</Preview>
### Usage
```jsx
import { TagList } from '@grafana/ui';
const tags = ['datasource-test', 'gdev', 'mysql', 'mssql'];
<TagList tags={tags} onClick={(tag) => console.log(tag)} />;
```
<Canvas of={TagListStories.List} />
### Props
@@ -1,8 +1,9 @@
import { Meta, Preview, ArgTypes } from '@storybook/blocks';
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import { Text } from './Text';
import { TextLink } from '../Link/TextLink.tsx';
import { Basic } from './Text.story.tsx';
import { Tooltip } from '../Tooltip';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
<Meta title="MDX|Text" component={Text} />
@@ -65,14 +66,14 @@ The following is the default behaviour and so, it will be applied according to i
The Text component is mainly comprised by itself. In occasions, the Text component can have another Text or TextLink component as a child.
<Preview>
<ExampleFrame>
<Text color="primary" element="p">
{'If you need more help of how to write in Grafana you can go to our '}
<TextLink href="https://grafana.com/docs/writers-toolkit/" external>
{'Writer’s Toolkit'}
</TextLink>
</Text>
</Preview>
</ExampleFrame>
```jsx
<Text color="primary" element="p">
@@ -83,12 +84,12 @@ The Text component is mainly comprised by itself. In occasions, the Text compone
</Text>
```
<Preview>
<ExampleFrame>
<Text color="primary" element="p">
{'And Forrest Gump said: '}
<Text italic>{"Life is like a box of chocolates. You never know what you're gonna get."}</Text>
</Text>
</Preview>
</ExampleFrame>
```jsx
<Text color="primary" element="p">
@@ -104,23 +105,23 @@ The Text component can be truncated. However, the Text component element rendere
1. The parent element is a Text component: the user just has to set the element prop to another value and the truncate prop to true.
As a result, the Text will be truncated but when the user hovers over it the full text will be seen on a tooltip.
<Preview>
<ExampleFrame>
<Text color="primary" element="p" truncate>
{'And Forrest Gump said: '}
<Text italic>{'Life is like a box of chocolates. You never know what you are gonna get.'}</Text>
</Text>
</Preview>
</ExampleFrame>
```jsx
<Text color="primary" element="p" truncate>
And Forrest Gump said:
<Text italic>{'Life is like a box of chocolates. You never know what you are gonna get.'}</Text>
<Text italic>Life is like a box of chocolates. You never know what you are gonna get.</Text>
</Text>
```
2. The parent element is not a Text component: the user has to add `overflow: hidden`, `text-overflow: ellipsis` and `whiteSpace: 'nowrap'` to it. In this case, the user should wrap up this container with a Tooltip, so when the text is truncated its content can still be seen hovering on the text.
<Preview>
<ExampleFrame>
<Tooltip content="This is a example of a span element truncated by its parent container">
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Text color="primary" variant="body">
@@ -128,7 +129,7 @@ The Text component can be truncated. However, the Text component element rendere
</Text>
</div>
</Tooltip>
</Preview>
</ExampleFrame>
```jsx
<Tooltip content="This is a example of a span element truncated by its parent container">
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
@@ -1,6 +1,8 @@
import { ArgTypes, Preview } from '@storybook/blocks';
import { ArgTypes, Canvas } from '@storybook/blocks';
import { Field } from '../Forms/Field';
import { TextArea } from './TextArea';
import * as TextAreaStories from './TextArea.story';
import { ExampleFrame } from '../../utils/storybook/ExampleFrame';
# TextArea
@@ -8,27 +10,19 @@ Use for multi line inputs like descriptions.
### Usage
```jsx
<TextArea invalid={invalid} placeholder={placeholder} cols={cols} disabled={disabled} />
```
<Canvas of={TextAreaStories.Basic} />
### Usage in forms with Field
`TextArea` should be used with the `Field` component to get labels and descriptions. It can also be used for validation by using the `required` attribute. See the `Field` component for more information.
```jsx
<Field label="Important information" description="This information is very important, so you really need to fill it in">
<TextArea name="importantTextarea" required />
</Field>
```
<Preview>
<ExampleFrame>
<Field
label="Important information"
description="This information is very important, so you really need to fill it in"
>
<TextArea name="importantTextarea" required />
</Field>
</Preview>
</ExampleFrame>
<ArgTypes of={TextArea} />
@@ -3,7 +3,7 @@
/** @jsxRuntime classic */
import { css, cx } from '@emotion/css';
import classnames from 'classnames';
import { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react';
import React, { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
@@ -159,6 +159,10 @@ const getStyles = (theme: GrafanaTheme2) => {
}),
},
[theme.breakpoints.down('md')]: {
width: 'auto !important',
},
'&:focus, &:focus-visible': {
...getFocusStyles(theme),
zIndex: 1,
@@ -1,6 +0,0 @@
import { ArgTypes } from '@storybook/blocks';
import { UnitPicker } from './UnitPicker';
# UnitPicker
<ArgTypes of={UnitPicker} />{' '}
@@ -2,7 +2,6 @@ import { action } from '@storybook/addon-actions';
import { Meta, StoryFn } from '@storybook/react';
import { UnitPicker, UnitPickerProps } from './UnitPicker';
import mdx from './UnitPicker.mdx';
const meta: Meta<typeof UnitPicker> = {
title: 'Pickers and Editors/UnitPicker',
@@ -11,7 +10,6 @@ const meta: Meta<typeof UnitPicker> = {
controls: {
exclude: ['onChange', 'value'],
},
docs: mdx,
},
};
@@ -322,3 +322,9 @@ export { type GraphNGLegendEvent } from '../graveyard/GraphNG/types';
export { ZoomPlugin } from '../graveyard/uPlot/plugins/ZoomPlugin';
export { TooltipPlugin } from '../graveyard/uPlot/plugins/TooltipPlugin';
export {
ElementSelectionContext,
useElementSelection,
type ElementSelectionContextState,
} from './ElementSelectionContext/ElementSelectionContext';
@@ -67,5 +67,11 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) {
},
},
},
'.dashboard-selected-element': {
outline: `2px dashed ${theme.colors.primary.border}`,
outlineOffset: '0px',
borderRadius: '2px',
},
});
}
@@ -0,0 +1,39 @@
import { css } from '@emotion/css';
import { Unstyled } from '@storybook/blocks';
import { ReactNode } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
interface ExampleFrameProps {
children: ReactNode;
}
/**
* Wraps children with a border for nicer presentation in Storybook docs.
*
* This is intended to be used temporarily to move away from our <Preview> patch until
* we have something more long term.
*/
export function ExampleFrame(props: ExampleFrameProps) {
const { children } = props;
const styles = useStyles2(getStyles);
return (
<Unstyled>
<div className={styles.container}>{children}</div>
</Unstyled>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: theme.shape.radius.default,
padding: theme.spacing(2),
marginBottom: theme.spacing(4),
}),
};
};
@@ -4,6 +4,7 @@ import * as React from 'react';
import { useDarkMode } from 'storybook-dark-mode';
import { GrafanaLight, GrafanaDark } from '../../../.storybook/storybookTheme';
import { GlobalStyles } from '../../themes';
type Props = {
context: DocsContextProps;
@@ -15,6 +16,7 @@ export const ThemedDocsContainer = ({ children, context }: Props) => {
return (
<DocsContainer theme={dark ? GrafanaDark : GrafanaLight} context={context}>
<GlobalStyles />
{children}
</DocsContainer>
);
+5 -5
View File
@@ -134,17 +134,17 @@ require (
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/tools v0.27.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
gonum.org/v1/gonum v0.15.1 // indirect
google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect
+10 -10
View File
@@ -381,8 +381,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
@@ -392,8 +392,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -403,8 +403,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
@@ -433,8 +433,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
@@ -452,8 +452,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+5 -2
View File
@@ -19,6 +19,7 @@ import (
glog "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/contexthandler"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -57,7 +58,8 @@ type httpClient interface {
func NewDataSourceProxy(ds *datasources.DataSource, pluginRoutes []*plugins.Route, ctx *contextmodel.ReqContext,
proxyPath string, cfg *setting.Cfg, clientProvider httpclient.Provider,
oAuthTokenService oauthtoken.OAuthTokenService, dsService datasources.DataSourceService,
tracer tracing.Tracer, features featuremgmt.FeatureToggles) (*DataSourceProxy, error) {
tracer tracing.Tracer, features featuremgmt.FeatureToggles,
) (*DataSourceProxy, error) {
targetURL, err := datasource.ValidateURL(ds.Type, ds.URL)
if err != nil {
return nil, err
@@ -261,7 +263,8 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
}
if proxy.oAuthTokenService.IsOAuthPassThruEnabled(proxy.ds) {
if token := proxy.oAuthTokenService.GetCurrentOAuthToken(req.Context(), proxy.ctx.SignedInUser); token != nil {
reqCtx := contexthandler.FromContext(req.Context())
if token := proxy.oAuthTokenService.GetCurrentOAuthToken(req.Context(), proxy.ctx.SignedInUser, reqCtx.UserToken); token != nil {
req.Header.Set("Authorization", fmt.Sprintf("%s %s", token.Type(), token.AccessToken))
idToken, ok := token.Extra("id_token").(string)
+4 -1
View File
@@ -32,7 +32,9 @@ import (
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
@@ -557,7 +559,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
var routes []*plugins.Route
proxy, err := setupDSProxyTest(t, ctx, ds, routes, "/path/to/folder/", func(proxy *DataSourceProxy) {
proxy.oAuthTokenService = &oauthtokentest.MockOauthTokenService{
GetCurrentOauthTokenFunc: func(_ context.Context, _ identity.Requester) *oauth2.Token {
GetCurrentOauthTokenFunc: func(_ context.Context, _ identity.Requester, _ *auth.UserToken) *oauth2.Token {
return (&oauth2.Token{
AccessToken: "testtoken",
RefreshToken: "testrefreshtoken",
@@ -573,6 +575,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
require.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
req = req.WithContext(context.WithValue(req.Context(), ctxkey.Key{}, &contextmodel.ReqContext{UserToken: nil}))
require.NoError(t, err)
proxy.director(req)
+2 -2
View File
@@ -35,8 +35,8 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
+4 -4
View File
@@ -77,8 +77,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -91,8 +91,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+3 -3
View File
@@ -78,13 +78,13 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/tools v0.27.0 // indirect
google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
+8 -8
View File
@@ -221,8 +221,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
@@ -241,8 +241,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
@@ -259,8 +259,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
@@ -277,8 +277,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+3 -3
View File
@@ -35,9 +35,9 @@ require (
go.opentelemetry.io/otel v1.32.0 // indirect; @grafana/grafana-backend-group
go.opentelemetry.io/otel/sdk v1.32.0 // indirect; @grafana/grafana-backend-group
go.opentelemetry.io/otel/trace v1.32.0 // indirect; @grafana/grafana-backend-group
golang.org/x/crypto v0.28.0 // indirect; @grafana/grafana-backend-group
golang.org/x/mod v0.21.0 // @grafana/grafana-backend-group
golang.org/x/net v0.30.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/crypto v0.29.0 // indirect; @grafana/grafana-backend-group
golang.org/x/mod v0.22.0 // @grafana/grafana-backend-group
golang.org/x/net v0.31.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/oauth2 v0.23.0 // @grafana/identity-access-team
golang.org/x/sync v0.9.0 // indirect; @grafana/alerting-backend
golang.org/x/text v0.20.0 // indirect; @grafana/grafana-backend-group
+6 -6
View File
@@ -262,8 +262,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
@@ -272,8 +272,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -283,8 +283,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+2 -2
View File
@@ -6,10 +6,10 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/subcommands v1.2.0
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
golang.org/x/tools v0.26.0
golang.org/x/tools v0.27.0
)
require (
golang.org/x/mod v0.21.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.9.0 // indirect
)
+4 -4
View File
@@ -4,9 +4,9 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
+3 -3
View File
@@ -111,12 +111,12 @@ require (
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/tools v0.27.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
gonum.org/v1/gonum v0.15.1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
+6 -6
View File
@@ -297,15 +297,15 @@ golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -337,8 +337,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+9 -1
View File
@@ -12,6 +12,7 @@ import (
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/datasources/service"
)
@@ -48,13 +49,15 @@ type queryParser struct {
legacy service.LegacyDataSourceLookup
reader *expr.ExpressionQueryReader
tracer tracing.Tracer
logger log.Logger
}
func newQueryParser(reader *expr.ExpressionQueryReader, legacy service.LegacyDataSourceLookup, tracer tracing.Tracer) *queryParser {
func newQueryParser(reader *expr.ExpressionQueryReader, legacy service.LegacyDataSourceLookup, tracer tracing.Tracer, logger log.Logger) *queryParser {
return &queryParser{
reader: reader,
legacy: legacy,
tracer: tracer,
logger: logger,
}
}
@@ -82,6 +85,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
ds, err := p.getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID)
if err != nil {
p.logger.Error("Failed to get valid datasource ref", "error", err)
return rsp, err
}
@@ -93,14 +97,17 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
// but this approach lets us focus on well typed behavior first
raw, err := json.Marshal(q)
if err != nil {
p.logger.Error("Failed to marshal query for expression", "error", err)
return rsp, err
}
iter, err := jsoniter.ParseBytes(jsoniter.ConfigDefault, raw)
if err != nil {
p.logger.Error("Failed to parse bytes for expression", "error", err)
return rsp, err
}
exp, err := p.reader.ReadQuery(q, iter)
if err != nil {
p.logger.Error("Failed to read query for expression", "error", err)
return rsp, NewErrorWithRefID(q.RefID, err)
}
exp.GraphID = int64(len(expressions) + 1)
@@ -170,6 +177,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
// Add the sorted expressions
sortedNodes, err := topo.SortStabilized(dg, nil)
if err != nil {
p.logger.Error("Error when sorting nodes", "error", err)
return rsp, makeCyclicError("")
}
for _, v := range sortedNodes {
+2 -1
View File
@@ -15,6 +15,7 @@ import (
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
@@ -29,7 +30,7 @@ type parserTestObject struct {
func TestQuerySplitting(t *testing.T) {
ctx := context.Background()
parser := newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()),
&legacyDataSourceRetriever{}, tracing.InitializeTracerForTest())
&legacyDataSourceRetriever{}, tracing.InitializeTracerForTest(), log.NewNopLogger())
t.Run("missing datasource flavors", func(t *testing.T) {
split, err := parser.parseRequest(ctx, &query.QueryDataRequest{
+1 -1
View File
@@ -26,7 +26,7 @@ func TestQueryRestConnectHandler(t *testing.T) {
},
tracer: tracing.InitializeTracerForTest(),
parser: newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()),
&legacyDataSourceRetriever{}, tracing.InitializeTracerForTest()),
&legacyDataSourceRetriever{}, tracing.InitializeTracerForTest(), nil),
log: log.New("test"),
}
qr := newQueryREST(b)
+1 -1
View File
@@ -75,7 +75,7 @@ func NewQueryAPIBuilder(features featuremgmt.FeatureToggles,
log: log.New("query_apiserver"),
client: client,
registry: registry,
parser: newQueryParser(reader, legacy, tracer),
parser: newQueryParser(reader, legacy, tracer, log.New("query_parser")),
metrics: newQueryMetrics(registerer),
tracer: tracer,
features: features,
+2 -1
View File
@@ -29,7 +29,8 @@ func NewScopeAPIBuilder() *ScopeAPIBuilder {
}
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, reg prometheus.Registerer) *ScopeAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
if !(features.IsEnabledGlobally(featuremgmt.FlagScopeApi) ||
features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) {
return nil // skip registration unless opting into experimental apis
}
builder := NewScopeAPIBuilder()
@@ -42,6 +42,9 @@ func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB,
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
created := time.Now()
updated := time.Now()
@@ -115,7 +115,7 @@ func folderTreeCollector(store db.DB) legacyTupleCollector {
func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector {
return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
query := `
SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name
SELECT u.uid as user_uid, u.is_service_account as is_service_account, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name
FROM permission p
INNER JOIN role r ON p.role_id = r.id
LEFT JOIN user_role ur ON r.id = ur.role_id
@@ -128,12 +128,13 @@ func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector
AND p.kind = ?
`
type Permission struct {
Action string `xorm:"action"`
Kind string
Identifier string
UserUID string `xorm:"user_uid"`
TeamUID string `xorm:"team_uid"`
BasicRoleName string `xorm:"basic_role_name"`
Action string `xorm:"action"`
Kind string
Identifier string
UserUID string `xorm:"user_uid"`
IsServiceAccount bool `xorm:"is_service_account"`
TeamUID string `xorm:"team_uid"`
BasicRoleName string `xorm:"basic_role_name"`
}
var permissions []Permission
@@ -149,7 +150,9 @@ func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector
for _, p := range permissions {
var subject string
if len(p.UserUID) > 0 {
if len(p.UserUID) > 0 && p.IsServiceAccount {
subject = zanzana.NewTupleEntry(zanzana.TypeServiceAccount, p.UserUID, "")
} else if len(p.UserUID) > 0 {
subject = zanzana.NewTupleEntry(zanzana.TypeUser, p.UserUID, "")
} else if len(p.TeamUID) > 0 {
subject = zanzana.NewTupleEntry(zanzana.TypeTeam, p.TeamUID, zanzana.RelationTeamMember)
@@ -198,16 +201,19 @@ func tupleStringWithoutCondition(tuple *openfgav1.TupleKey) string {
func basicRoleBindingsCollector(store db.DB) legacyTupleCollector {
return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
query := `
SELECT ou.org_id, u.uid as user_uid, ou.role as org_role
SELECT
ou.org_id, u.uid as user_uid,
u.is_service_account as is_service_account,
ou.role as org_role
FROM org_user ou
LEFT JOIN ` + store.GetDialect().Quote("user") + ` u ON u.id = ou.user_id
WHERE ou.org_id = ?
AND NOT u.is_service_account
`
// FIXME: handle service admin role
type Binding struct {
UserUID string `xorm:"user_uid"`
OrgRole string `xorm:"org_role"`
UserUID string `xorm:"user_uid"`
IsServiceAccount bool `xorm:"is_service_account"`
OrgRole string `xorm:"org_role"`
}
var bindings []Binding
@@ -222,8 +228,13 @@ func basicRoleBindingsCollector(store db.DB) legacyTupleCollector {
tuples := make(map[string]map[string]*openfgav1.TupleKey)
for _, b := range bindings {
userType := zanzana.TypeUser
if b.IsServiceAccount {
userType = zanzana.TypeServiceAccount
}
tuple := &openfgav1.TupleKey{
User: zanzana.NewTupleEntry(zanzana.TypeUser, b.UserUID, ""),
User: zanzana.NewTupleEntry(userType, b.UserUID, ""),
Relation: zanzana.RelationAssignee,
Object: zanzana.NewTupleEntry(zanzana.TypeRole, zanzana.TranslateBasicRole(b.OrgRole), ""),
}
@@ -286,7 +297,7 @@ func teamRoleBindingsCollector(store db.DB) legacyTupleCollector {
func userRoleBindingsCollector(store db.DB) legacyTupleCollector {
return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
query := `
SELECT u.uid AS user_uid, r.uid AS role_uid
SELECT u.uid AS user_uid, u.is_service_account as is_service_account, r.uid AS role_uid
FROM user_role ur
INNER JOIN ` + store.GetDialect().Quote("user") + ` u ON ur.user_id = u.id
INNER JOIN role r ON ur.role_id = r.id
@@ -294,8 +305,9 @@ func userRoleBindingsCollector(store db.DB) legacyTupleCollector {
AND r.name NOT LIKE 'managed:%'
`
type Binding struct {
UserUID string `xorm:"user_uid"`
RoleUID string `xorm:"role_uid"`
UserUID string `xorm:"user_uid"`
IsServiceAccount bool `xorm:"is_service_account"`
RoleUID string `xorm:"role_uid"`
}
var bindings []Binding
@@ -310,8 +322,13 @@ func userRoleBindingsCollector(store db.DB) legacyTupleCollector {
tuples := make(map[string]map[string]*openfgav1.TupleKey)
for _, b := range bindings {
userType := zanzana.TypeUser
if b.IsServiceAccount {
userType = zanzana.TypeServiceAccount
}
tuple := &openfgav1.TupleKey{
User: zanzana.NewTupleEntry(zanzana.TypeUser, b.UserUID, ""),
User: zanzana.NewTupleEntry(userType, b.UserUID, ""),
Relation: zanzana.RelationAssignee,
Object: zanzana.NewTupleEntry(zanzana.TypeRole, b.RoleUID, ""),
}
@@ -397,7 +414,8 @@ func zanzanaCollector(relations []string) zanzanaTupleCollector {
for c != "" {
res, err := client.Read(ctx, &authzextv1.ReadRequest{
Namespace: namespace,
ContinuationToken: c,
Namespace: namespace,
TupleKey: &authzextv1.ReadRequestTupleKey{
Object: object,
Relation: relation,
+4 -1
View File
@@ -74,12 +74,15 @@ type CreateTokenCommand struct {
}
// UserTokenService are used for generating and validating user tokens
//
//go:generate mockery --name UserTokenService --structname MockUserAuthTokenService --outpkg authtest --filename auth_token_service_mock.go --output ./authtest/
type UserTokenService interface {
CreateToken(ctx context.Context, cmd *CreateTokenCommand) (*UserToken, error)
LookupToken(ctx context.Context, unhashedToken string) (*UserToken, error)
GetTokenByExternalSessionID(ctx context.Context, externalSessionID int64) (*UserToken, error)
GetExternalSession(ctx context.Context, extSessionID int64) (*ExternalSession, error)
GetExternalSession(ctx context.Context, externalSessionID int64) (*ExternalSession, error)
FindExternalSessions(ctx context.Context, query *ListExternalSessionQuery) ([]*ExternalSession, error)
UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *UpdateExternalSessionCommand) error
// RotateToken will always rotate a valid token
RotateToken(ctx context.Context, cmd RotateCommand) (*UserToken, error)
+6 -2
View File
@@ -248,14 +248,18 @@ func (s *UserAuthTokenService) GetTokenByExternalSessionID(ctx context.Context,
return &userToken, err
}
func (s *UserAuthTokenService) GetExternalSession(ctx context.Context, extSessionID int64) (*auth.ExternalSession, error) {
return s.externalSessionStore.Get(ctx, extSessionID)
func (s *UserAuthTokenService) GetExternalSession(ctx context.Context, externalSessionID int64) (*auth.ExternalSession, error) {
return s.externalSessionStore.Get(ctx, externalSessionID)
}
func (s *UserAuthTokenService) FindExternalSessions(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) {
return s.externalSessionStore.List(ctx, query)
}
func (s *UserAuthTokenService) UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error {
return s.externalSessionStore.Update(ctx, externalSessionID, cmd)
}
func (s *UserAuthTokenService) RotateToken(ctx context.Context, cmd auth.RotateCommand) (*auth.UserToken, error) {
if cmd.UnHashedToken == "" {
return nil, auth.ErrInvalidSessionToken
@@ -27,11 +27,11 @@ func provideExternalSessionStore(sqlStore db.DB, secretService secrets.Service,
}
}
func (s *store) Get(ctx context.Context, extSessionID int64) (*auth.ExternalSession, error) {
func (s *store) Get(ctx context.Context, ID int64) (*auth.ExternalSession, error) {
ctx, span := s.tracer.Start(ctx, "externalsession.Get")
defer span.End()
externalSession := &auth.ExternalSession{ID: extSessionID}
externalSession := &auth.ExternalSession{ID: ID}
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
found, err := sess.Get(externalSession)
@@ -150,6 +150,45 @@ func (s *store) Create(ctx context.Context, extSession *auth.ExternalSession) er
return nil
}
func (s *store) Update(ctx context.Context, ID int64, cmd *auth.UpdateExternalSessionCommand) error {
ctx, span := s.tracer.Start(ctx, "externalsession.Update")
defer span.End()
var err error
externalSession := &auth.ExternalSession{}
externalSession.AccessToken, err = s.encryptAndEncode(cmd.Token.AccessToken)
if err != nil {
return err
}
externalSession.RefreshToken, err = s.encryptAndEncode(cmd.Token.RefreshToken)
if err != nil {
return err
}
var secretIdToken string
if idToken, ok := cmd.Token.Extra("id_token").(string); ok && idToken != "" {
secretIdToken, err = s.encryptAndEncode(idToken)
if err != nil {
return err
}
externalSession.IDToken = secretIdToken
}
externalSession.ExpiresAt = cmd.Token.Expiry
err = s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
_, err := sess.ID(ID).Cols("access_token", "refresh_token", "id_token", "expires_at").Update(externalSession)
return err
})
if err != nil {
return err
}
return nil
}
func (s *store) Delete(ctx context.Context, ID int64) error {
ctx, span := s.tracer.Start(ctx, "externalsession.Delete")
defer span.End()
@@ -0,0 +1,384 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
package authtest
import (
context "context"
auth "github.com/grafana/grafana/pkg/services/auth"
mock "github.com/stretchr/testify/mock"
usertoken "github.com/grafana/grafana/pkg/models/usertoken"
)
// MockUserAuthTokenService is an autogenerated mock type for the UserTokenService type
type MockUserAuthTokenService struct {
mock.Mock
}
// ActiveTokenCount provides a mock function with given fields: ctx, userID
func (_m *MockUserAuthTokenService) ActiveTokenCount(ctx context.Context, userID *int64) (int64, error) {
ret := _m.Called(ctx, userID)
if len(ret) == 0 {
panic("no return value specified for ActiveTokenCount")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *int64) (int64, error)); ok {
return rf(ctx, userID)
}
if rf, ok := ret.Get(0).(func(context.Context, *int64) int64); ok {
r0 = rf(ctx, userID)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok {
r1 = rf(ctx, userID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateToken provides a mock function with given fields: ctx, cmd
func (_m *MockUserAuthTokenService) CreateToken(ctx context.Context, cmd *auth.CreateTokenCommand) (*usertoken.UserToken, error) {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for CreateToken")
}
var r0 *usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *auth.CreateTokenCommand) (*usertoken.UserToken, error)); ok {
return rf(ctx, cmd)
}
if rf, ok := ret.Get(0).(func(context.Context, *auth.CreateTokenCommand) *usertoken.UserToken); ok {
r0 = rf(ctx, cmd)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *auth.CreateTokenCommand) error); ok {
r1 = rf(ctx, cmd)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindExternalSessions provides a mock function with given fields: ctx, query
func (_m *MockUserAuthTokenService) FindExternalSessions(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for FindExternalSessions")
}
var r0 []*auth.ExternalSession
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error)); ok {
return rf(ctx, query)
}
if rf, ok := ret.Get(0).(func(context.Context, *auth.ListExternalSessionQuery) []*auth.ExternalSession); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*auth.ExternalSession)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *auth.ListExternalSessionQuery) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetExternalSession provides a mock function with given fields: ctx, externalSessionID
func (_m *MockUserAuthTokenService) GetExternalSession(ctx context.Context, externalSessionID int64) (*auth.ExternalSession, error) {
ret := _m.Called(ctx, externalSessionID)
if len(ret) == 0 {
panic("no return value specified for GetExternalSession")
}
var r0 *auth.ExternalSession
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*auth.ExternalSession, error)); ok {
return rf(ctx, externalSessionID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *auth.ExternalSession); ok {
r0 = rf(ctx, externalSessionID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*auth.ExternalSession)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, externalSessionID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTokenByExternalSessionID provides a mock function with given fields: ctx, externalSessionID
func (_m *MockUserAuthTokenService) GetTokenByExternalSessionID(ctx context.Context, externalSessionID int64) (*usertoken.UserToken, error) {
ret := _m.Called(ctx, externalSessionID)
if len(ret) == 0 {
panic("no return value specified for GetTokenByExternalSessionID")
}
var r0 *usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*usertoken.UserToken, error)); ok {
return rf(ctx, externalSessionID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *usertoken.UserToken); ok {
r0 = rf(ctx, externalSessionID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, externalSessionID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetUserRevokedTokens provides a mock function with given fields: ctx, userID
func (_m *MockUserAuthTokenService) GetUserRevokedTokens(ctx context.Context, userID int64) ([]*usertoken.UserToken, error) {
ret := _m.Called(ctx, userID)
if len(ret) == 0 {
panic("no return value specified for GetUserRevokedTokens")
}
var r0 []*usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*usertoken.UserToken, error)); ok {
return rf(ctx, userID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) []*usertoken.UserToken); ok {
r0 = rf(ctx, userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, userID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetUserToken provides a mock function with given fields: ctx, userID, userTokenID
func (_m *MockUserAuthTokenService) GetUserToken(ctx context.Context, userID int64, userTokenID int64) (*usertoken.UserToken, error) {
ret := _m.Called(ctx, userID, userTokenID)
if len(ret) == 0 {
panic("no return value specified for GetUserToken")
}
var r0 *usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (*usertoken.UserToken, error)); ok {
return rf(ctx, userID, userTokenID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *usertoken.UserToken); ok {
r0 = rf(ctx, userID, userTokenID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok {
r1 = rf(ctx, userID, userTokenID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetUserTokens provides a mock function with given fields: ctx, userID
func (_m *MockUserAuthTokenService) GetUserTokens(ctx context.Context, userID int64) ([]*usertoken.UserToken, error) {
ret := _m.Called(ctx, userID)
if len(ret) == 0 {
panic("no return value specified for GetUserTokens")
}
var r0 []*usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*usertoken.UserToken, error)); ok {
return rf(ctx, userID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) []*usertoken.UserToken); ok {
r0 = rf(ctx, userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, userID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LookupToken provides a mock function with given fields: ctx, unhashedToken
func (_m *MockUserAuthTokenService) LookupToken(ctx context.Context, unhashedToken string) (*usertoken.UserToken, error) {
ret := _m.Called(ctx, unhashedToken)
if len(ret) == 0 {
panic("no return value specified for LookupToken")
}
var r0 *usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (*usertoken.UserToken, error)); ok {
return rf(ctx, unhashedToken)
}
if rf, ok := ret.Get(0).(func(context.Context, string) *usertoken.UserToken); ok {
r0 = rf(ctx, unhashedToken)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, unhashedToken)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RevokeAllUserTokens provides a mock function with given fields: ctx, userID
func (_m *MockUserAuthTokenService) RevokeAllUserTokens(ctx context.Context, userID int64) error {
ret := _m.Called(ctx, userID)
if len(ret) == 0 {
panic("no return value specified for RevokeAllUserTokens")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, userID)
} else {
r0 = ret.Error(0)
}
return r0
}
// RevokeToken provides a mock function with given fields: ctx, token, soft
func (_m *MockUserAuthTokenService) RevokeToken(ctx context.Context, token *usertoken.UserToken, soft bool) error {
ret := _m.Called(ctx, token, soft)
if len(ret) == 0 {
panic("no return value specified for RevokeToken")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *usertoken.UserToken, bool) error); ok {
r0 = rf(ctx, token, soft)
} else {
r0 = ret.Error(0)
}
return r0
}
// RotateToken provides a mock function with given fields: ctx, cmd
func (_m *MockUserAuthTokenService) RotateToken(ctx context.Context, cmd auth.RotateCommand) (*usertoken.UserToken, error) {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for RotateToken")
}
var r0 *usertoken.UserToken
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, auth.RotateCommand) (*usertoken.UserToken, error)); ok {
return rf(ctx, cmd)
}
if rf, ok := ret.Get(0).(func(context.Context, auth.RotateCommand) *usertoken.UserToken); ok {
r0 = rf(ctx, cmd)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*usertoken.UserToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, auth.RotateCommand) error); ok {
r1 = rf(ctx, cmd)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateExternalSession provides a mock function with given fields: ctx, externalSessionID, cmd
func (_m *MockUserAuthTokenService) UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error {
ret := _m.Called(ctx, externalSessionID, cmd)
if len(ret) == 0 {
panic("no return value specified for UpdateExternalSession")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, *auth.UpdateExternalSessionCommand) error); ok {
r0 = rf(ctx, externalSessionID, cmd)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewMockUserAuthTokenService creates a new instance of MockUserAuthTokenService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockUserAuthTokenService(t interface {
mock.TestingT
Cleanup(func())
}) *MockUserAuthTokenService {
mock := &MockUserAuthTokenService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -147,6 +147,24 @@ func (_m *MockExternalSessionStore) List(ctx context.Context, query *auth.ListEx
return r0, r1
}
// Update provides a mock function with given fields: ctx, ID, cmd
func (_m *MockExternalSessionStore) Update(ctx context.Context, ID int64, cmd *auth.UpdateExternalSessionCommand) error {
ret := _m.Called(ctx, ID, cmd)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, *auth.UpdateExternalSessionCommand) error); ok {
r0 = rf(ctx, ID, cmd)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewMockExternalSessionStore creates a new instance of MockExternalSessionStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockExternalSessionStore(t interface {
+7 -10
View File
@@ -14,12 +14,15 @@ import (
"github.com/grafana/grafana/pkg/services/login"
)
var _ auth.UserTokenService = (*FakeUserAuthTokenService)(nil)
type FakeUserAuthTokenService struct {
CreateTokenProvider func(ctx context.Context, cmd *auth.CreateTokenCommand) (*auth.UserToken, error)
RotateTokenProvider func(ctx context.Context, cmd auth.RotateCommand) (*auth.UserToken, error)
GetTokenByExternalSessionIDProvider func(ctx context.Context, externalSessionID int64) (*auth.UserToken, error)
GetExternalSessionProvider func(ctx context.Context, externalSessionID int64) (*auth.ExternalSession, error)
FindExternalSessionsProvider func(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error)
UpdateExternalSessionProvider func(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error
TryRotateTokenProvider func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, *auth.UserToken, error)
LookupTokenProvider func(ctx context.Context, unhashedToken string) (*auth.UserToken, error)
RevokeTokenProvider func(ctx context.Context, token *auth.UserToken, soft bool) error
@@ -98,6 +101,10 @@ func (s *FakeUserAuthTokenService) FindExternalSessions(ctx context.Context, que
return s.FindExternalSessionsProvider(context.Background(), query)
}
func (s *FakeUserAuthTokenService) UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error {
return s.UpdateExternalSessionProvider(context.Background(), externalSessionID, cmd)
}
func (s *FakeUserAuthTokenService) LookupToken(ctx context.Context, unhashedToken string) (*auth.UserToken, error) {
return s.LookupTokenProvider(context.Background(), unhashedToken)
}
@@ -149,16 +156,6 @@ func (ts *FakeOAuthTokenService) IsOAuthPassThruEnabled(*datasources.DataSource)
return ts.passThruEnabled
}
func (ts *FakeOAuthTokenService) HasOAuthEntry(context.Context, identity.Requester) (*login.UserAuth, bool, error) {
if ts.ExpectedAuthUser != nil {
return ts.ExpectedAuthUser, true, nil
}
if error, ok := ts.ExpectedErrors["HasOAuthEntry"]; ok {
return nil, false, error
}
return nil, false, nil
}
func (ts *FakeOAuthTokenService) InvalidateOAuthTokens(ctx context.Context, usr *login.UserAuth) error {
ts.ExpectedAuthUser.OAuthAccessToken = ""
ts.ExpectedAuthUser.OAuthRefreshToken = ""
+8
View File
@@ -3,6 +3,8 @@ package auth
import (
"context"
"time"
"golang.org/x/oauth2"
)
type ExternalSession struct {
@@ -43,6 +45,10 @@ func (e *ExternalSession) Clone() *ExternalSession {
}
}
type UpdateExternalSessionCommand struct {
Token *oauth2.Token
}
type ListExternalSessionQuery struct {
ID int64
NameID string
@@ -57,6 +63,8 @@ type ExternalSessionStore interface {
List(ctx context.Context, query *ListExternalSessionQuery) ([]*ExternalSession, error)
// Create creates a new external session for a user
Create(ctx context.Context, extSesion *ExternalSession) error
// Update updates an external session
Update(ctx context.Context, ID int64, cmd *UpdateExternalSessionCommand) error
// Delete deletes an external session
Delete(ctx context.Context, ID int64) error
// DeleteExternalSessionsByUserID deletes an external session
+1 -1
View File
@@ -178,7 +178,7 @@ type RedirectClient interface {
// that should happen during logout and supports client specific redirect URL.
type LogoutClient interface {
Client
Logout(ctx context.Context, user identity.Requester) (*Redirect, bool)
Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*Redirect, bool)
}
type SSOSettingsAwareClient interface {
+3 -3
View File
@@ -37,7 +37,7 @@ func ProvideRegistration(
jwtService auth.JWTVerifierService, userProtectionService login.UserProtectionService,
loginAttempts loginattempt.Service, quotaService quota.Service,
authInfoService login.AuthInfoService, renderService rendering.Service,
features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService,
features featuremgmt.FeatureToggles, oauthTokenService oauthtoken.OAuthTokenService,
socialService social.Service, cache *remotecache.RemoteCache,
ldapService service.LDAP, settingsProviderService setting.Provider,
tracer tracing.Tracer, tempUserService tempuser.Service, notificationService notifications.Service,
@@ -108,13 +108,13 @@ func ProvideRegistration(
}
// FIXME (jguer): move to User package
userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService, tracer)
userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService, tracer, features)
orgSync := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg, tracer)
authnSvc.RegisterPostAuthHook(userSync.SyncUserHook, 10)
authnSvc.RegisterPostAuthHook(userSync.EnableUserHook, 20)
authnSvc.RegisterPostAuthHook(orgSync.SyncOrgRolesHook, 30)
authnSvc.RegisterPostAuthHook(userSync.SyncLastSeenHook, 130)
authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService, tracer).SyncOauthTokenHook, 60)
authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService, tracer, features).SyncOauthTokenHook, 60)
authnSvc.RegisterPostAuthHook(userSync.FetchSyncedUserHook, 100)
rbacSync := sync.ProvideRBACSync(accessControlService, tracer, permRegistry)
+1 -1
View File
@@ -322,7 +322,7 @@ func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionTo
goto Default
}
clientRedirect, ok := logoutClient.Logout(ctx, user)
clientRedirect, ok := logoutClient.Logout(ctx, user, sessionToken)
if !ok {
goto Default
}
+1 -1
View File
@@ -507,7 +507,7 @@ func TestService_Logout(t *testing.T) {
expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"},
client: &authntest.MockClient{
NameFunc: func() string { return "auth.client.azuread" },
LogoutFunc: func(ctx context.Context, _ identity.Requester) (*authn.Redirect, bool) {
LogoutFunc: func(ctx context.Context, _ identity.Requester, sessionToken *usertoken.UserToken) (*authn.Redirect, bool) {
return &authn.Redirect{URL: "http://idp.com/logout"}, true
},
},

Some files were not shown because too many files have changed in this diff Show More