Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync
This commit is contained in:
+12
-5
@@ -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"]
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||

|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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=
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+52
@@ -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
-1
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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=
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user