diff --git a/.betterer.results b/.betterer.results
index 408d11a39d1..6df3eefe0a5 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -3337,11 +3337,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx:5381": [
- [0, 0, 0, "Do not use any type assertions.", "0"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "2"],
- [0, 0, 0, "Do not use any type assertions.", "3"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "4"]
+ [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
+ [0, 0, 0, "Do not use any type assertions.", "1"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@@ -4528,9 +4526,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
- "public/app/features/inspector/InspectDataTab.tsx:5381": [
- [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
- ],
"public/app/features/inspector/InspectErrorTab.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
@@ -4651,6 +4646,13 @@ exports[`better eslint`] = {
"public/app/features/logs/components/logParser.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
+ "public/app/features/logs/utils.ts:5381": [
+ [0, 0, 0, "Do not use any type assertions.", "0"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
+ [0, 0, 0, "Do not use any type assertions.", "2"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "3"],
+ [0, 0, 0, "Do not use any type assertions.", "4"]
+ ],
"public/app/features/manage-dashboards/DashboardImportPage.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
diff --git a/.eslintrc b/.eslintrc
index d57ff41338a..3d3cae6a0fd 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -113,7 +113,7 @@
}
],
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
- "jsx-a11y/no-noninteractive-tabindex": "off",
+ "jsx-a11y/no-noninteractive-tabindex": "error",
"jsx-a11y/no-redundant-roles": "error",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/role-has-required-aria-props": "error",
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 1d68a983622..58222f07297 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -16,9 +16,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync
- - name: generate-packages-docs
- uses: actions/setup-node@v3.4.0
- id: generate-docs
+ - name: setup node
+ uses: actions/setup-node@v3.4.0
with:
node-version: '16'
- name: Get yarn cache directory path
diff --git a/.gitignore b/.gitignore
index 865bc7dd70b..cf9fe79b44d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,6 +130,7 @@ pkg/cmd/grafana-server/__debug_bin
/packages/**/.rpt2_cache
/packages/**/tsdoc-metadata.json
/packages/**/package.tgz
+/packages/grafana-toolkit/sass
## CI places the packages in a different location
/npm-artifacts/*.tgz
diff --git a/contribute/engineering/backend/instrumentation.md b/contribute/engineering/backend/instrumentation.md
index b8b77b473dd..c6957d6c969 100644
--- a/contribute/engineering/backend/instrumentation.md
+++ b/contribute/engineering/backend/instrumentation.md
@@ -55,6 +55,8 @@ When to use which log level?
Use a contextual logger to include additional key/value pairs attached to `context.Context`, e.g. `traceID`, to allow correlating logs with traces and/or correlate logs with a common identifier.
+You must [Enable tracing in Grafana](#2-enable-tracing-in-grafana) to get a traceID
+
Example:
```go
@@ -241,36 +243,38 @@ Be **careful** to not expose any sensitive information in span names, attribute
### How to collect, visualize and query traces (and correlate logs with traces) locally
-1. Start Jaeger
+#### 1. Start Jaeger
- ```bash
- make devenv sources=jaeger
- ```
+```bash
+make devenv sources=jaeger
+```
-2. Enable tracing in Grafana
+#### 2. Enable tracing in Grafana
- opentelemetry tracing (recommended):
+To enable tracing in Grafana, you must set the address in your config.ini file
- ```ini
- [tracing.opentelemetry.jaeger]
- address = http://localhost:14268/api/traces
- ```
+opentelemetry tracing (recommended):
- opentracing tracing (deprecated/not recommended):
+```ini
+[tracing.opentelemetry.jaeger]
+address = http://localhost:14268/api/traces
+```
- ```ini
- [tracing.jaeger]
- address = localhost:6831
- ```
+opentracing tracing (deprecated/not recommended):
-3. Search/browse collected logs and traces in Grafana Explore
+```ini
+[tracing.jaeger]
+address = localhost:6831
+```
- You need provisioned gdev-jaeger and gdev-loki datasources, see [developer dashboard and data sources](https://github.com/grafana/grafana/tree/main/devenv#developer-dashboards-and-data-sources) for setup instructions.
+#### 3. Search/browse collected logs and traces in Grafana Explore
- Open Grafana explore and select gdev-loki datasource and use the query `{filename="/var/log/grafana/grafana.log"} | logfmt`.
+You need provisioned gdev-jaeger and gdev-loki datasources, see [developer dashboard and data sources](https://github.com/grafana/grafana/tree/main/devenv#developer-dashboards-and-data-sources) for setup instructions.
- You can then inspect any log message that includes a `traceID` and from there click on `gdev-jaeger` to split view and inspect the trace in question.
+Open Grafana explore and select gdev-loki datasource and use the query `{filename="/var/log/grafana/grafana.log"} | logfmt`.
-4. Search/browse collected traces in Jaeger UI
+You can then inspect any log message that includes a `traceID` and from there click on `gdev-jaeger` to split view and inspect the trace in question.
- You can open http://localhost:16686 to use the Jaeger UI for browsing and searching traces.
+#### 4. Search/browse collected traces in Jaeger UI
+
+You can open http://localhost:16686 to use the Jaeger UI for browsing and searching traces.
diff --git a/docs/sources/alerting/fundamentals/_index.md b/docs/sources/alerting/fundamentals/_index.md
index 77ae41cb259..56e87c448fa 100644
--- a/docs/sources/alerting/fundamentals/_index.md
+++ b/docs/sources/alerting/fundamentals/_index.md
@@ -9,7 +9,7 @@ weight: 105
# Explore Grafana Alerting
-Whether you're starting or expanding your implementation of Grafana Alerting, learn more about the key concepts and available features that help you create, manage, and take action on your alerts and improve your team’s ability to resolve issues quickly.
+Learn about the key concepts and features that help you create, manage, and take action on your alerts and improve your team's ability to resolve issues quickly.
- [Data sources](https://grafana.com/docs/grafana/latest/alerting/fundamentals/data-source-alerting/)
- [Alert rules](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/)
diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md
index 4401baedfeb..a448cd1fb26 100644
--- a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md
+++ b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md
@@ -96,19 +96,17 @@ EOT
}
```
-1. Enter text for your notification in the text field.
+2. Enter text for your notification in the text field.
The `text` field supports [Go-style templating](https://pkg.go.dev/text/template). This enables you to manage your Grafana Alerting message templates directly in Terraform.
-1. Run the command ‘terraform apply’.
+3. Run the command ‘terraform apply’.
-1. Go to the Grafana UI and check the details of your contact point.
-
-**Note:**
+4. Go to the Grafana UI and check the details of your contact point.
You cannot edit resources provisioned via Terraform from the UI. This ensures that your alerting stack always stays in sync with your code.
-1. Click **Test** to verify that the contact point works correctly.
+5. Click **Test** to verify that the contact point works correctly.
**Note:**
@@ -172,17 +170,17 @@ contact_point = grafana_contact_point.my_slack_contact_point.name
}
-1. In the mute_timings field, link a mute timing to your notification policy.
+2. In the mute_timings field, link a mute timing to your notification policy.
-1. Run the command ‘terraform apply’.
+3. Run the command ‘terraform apply’.
-1. Go to the Grafana UI and check the details of your notification policy.
+4. Go to the Grafana UI and check the details of your notification policy.
**Note:**
You cannot edit resources provisioned from Terraform from the UI. This ensures that your alerting stack always stays in sync with your code.
-1. Click **Test** to verify that the notification point is working correctly.
+5. Click **Test** to verify that the notification point is working correctly.
## Provision mute timings
@@ -209,16 +207,16 @@ name = "My Mute Timing"
}
-1. Run the command ‘terraform apply’.
-1. Go to the Grafana UI and check the details of your mute timing.
-1. Reference your newly created mute timing in a notification policy using the `mute_timings` field.
+2. Run the command ‘terraform apply’.
+3. Go to the Grafana UI and check the details of your mute timing.
+4. Reference your newly created mute timing in a notification policy using the `mute_timings` field.
This will apply your mute timing to some or all of your notifications.
**Note:**
You cannot edit resources provisioned from Terraform from the UI. This ensures that your alerting stack always stays in sync with your code.
-1. Click **Test** to verify that the mute timing is working correctly.
+5. Click **Test** to verify that the mute timing is working correctly.
## Provision alert rules
@@ -243,11 +241,11 @@ resource "grafana_folder" "rule_folder" {
}
```
-1. Define an alert rule.
+2. Define an alert rule.
For more information on alert rules, refer to [how to create Grafana-managed alerts](https://grafana.com/blog/2022/08/01/grafana-alerting-video-how-to-create-alerts-in-grafana-9/).
-1. Create a rule group containing one or more rules.
+3. Create a rule group containing one or more rules.
In this example, the `grafana_rule_group` resource group is used.
@@ -314,7 +312,7 @@ EOT
}
```
-1. Go to the Grafana UI and check your alert rule.
+4. Go to the Grafana UI and check your alert rule.
You can see whether or not the alert rule is firing. You can also see a visualization of each of the alert rule’s query stages
diff --git a/docs/sources/dashboards/dashboard-public.md b/docs/sources/dashboards/dashboard-public.md
index d39db17bf7a..b26b2e19df4 100644
--- a/docs/sources/dashboards/dashboard-public.md
+++ b/docs/sources/dashboards/dashboard-public.md
@@ -59,5 +59,6 @@ publicDashboards = true
- Exemplars will be omitted from the panel.
- Annotations will not be displayed in public dashboards.
- Grafana Live and real-time event streams are not supported.
+- Library panels are currently not supported, but are planned to be in the future.
We are excited to share this enhancement with you and we’d love your feedback! Please check out the [Github](https://github.com/grafana/grafana/discussions/49253) discussion and join the conversation.
diff --git a/docs/sources/developers/http_api/reporting.md b/docs/sources/developers/http_api/reporting.md
index 12ffa51ed33..19fcb80d471 100644
--- a/docs/sources/developers/http_api/reporting.md
+++ b/docs/sources/developers/http_api/reporting.md
@@ -15,16 +15,416 @@ title: Reporting API
This API allows you to interact programmatically with the [Reporting]({{< relref "../../dashboards/create-reports/" >}}) feature.
+> The Reporting API is not stabilized yet, it is still in active development and may change without prior notice.
+
> Reporting is only available in Grafana Enterprise. Read more about [Grafana Enterprise]({{< relref "../../enterprise/" >}}).
> If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions]({{< relref "../../administration/roles-and-permissions/access-control/custom-role-actions-scopes/" >}}) for more information.
+## List all reports
+
+`GET /api/reports`
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| ------------ | --------------------------- |
+| reports:read | reports:\* reports:id:\* |
+
+### Example request
+
+```http
+GET /api/reports HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 1840
+
+[
+ {
+ "id": 2,
+ "userId": 1,
+ "orgId": 1,
+ "name": "Report 2",
+ "recipients": "example-report@grafana.com",
+ "replyTo": "",
+ "message": "Hi, \nPlease find attached a PDF status report. If you have any questions, feel free to contact me!\nBest,",
+ "schedule": {
+ "startDate": "2022-10-02T00:00:00+02:00",
+ "endDate": null,
+ "frequency": "once",
+ "intervalFrequency": "",
+ "intervalAmount": 0,
+ "workdaysOnly": false,
+ "dayOfMonth": "2",
+ "timeZone": "Europe/Warsaw"
+ },
+ "options": {
+ "orientation": "landscape",
+ "layout": "grid",
+ },
+ "enableDashboardUrl": true,
+ "state": "scheduled",
+ "dashboards": [
+ {
+ "dashboard": {
+ "id": 463,
+ "uid": "7MeksYbmk",
+ "name": "Alerting with TestData"
+ },
+ "reportVariables": {
+ "namefilter": "TestData"
+ }
+ }
+ ],
+ "formats": [
+ "pdf",
+ "csv"
+ ],
+ "created": "2022-09-19T11:44:42+02:00",
+ "updated": "2022-09-19T11:44:42+02:00"
+ }
+]
+```
+
+### Status Codes
+
+- **200** – OK
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **500** – Unexpected error or server misconfiguration. Refer to server logs for more details.
+
+## Get a report
+
+`GET /api/reports/:id`
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| ------------ | ---------------------------------------------------------- |
+| reports:read | reports:\* reports:id:\* reports:id:1(single report) |
+
+### Example request
+
+```http
+GET /api/reports/2 HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 940
+
+{
+ "id": 2,
+ "userId": 1,
+ "orgId": 1,
+ "name": "Report 2",
+ "recipients": "example-report@grafana.com",
+ "replyTo": "",
+ "message": "Hi, \nPlease find attached a PDF status report. If you have any questions, feel free to contact me!\nBest,",
+ "schedule": {
+ "startDate": "2022-10-02T00:00:00+02:00",
+ "endDate": null,
+ "frequency": "once",
+ "intervalFrequency": "",
+ "intervalAmount": 0,
+ "workdaysOnly": false,
+ "dayOfMonth": "2",
+ "timeZone": "Europe/Warsaw"
+ },
+ "options": {
+ "orientation": "landscape",
+ "layout": "grid",
+ },
+ "enableDashboardUrl": true,
+ "state": "scheduled",
+ "dashboards": [
+ {
+ "dashboard": {
+ "id": 463,
+ "uid": "7MeksYbmk",
+ "name": "Alerting with TestData"
+ },
+ "timeRange": {
+ "from": "",
+ "to": ""
+ },
+ "reportVariables": {
+ "namefilter": "TestData"
+ }
+ }
+ ],
+ "formats": [
+ "pdf",
+ "csv"
+ ],
+ "created": "2022-09-12T11:44:42+02:00",
+ "updated": "2022-09-12T11:44:42+02:00"
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **400** – Bad request (invalid report ID).
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **403** – Forbidden (access denied to a report or a dashboard used in the report).
+- **404** – Not found (such report does not exist).
+- **500** – Unexpected error or server misconfiguration. Refer to server logs for more details.
+
+## Create a report
+
+`POST /api/reports`
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| -------------- | ----- |
+| reports:create | n/a |
+
+### Example request
+
+```http
+POST /api/reports HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+{
+ "name": "Report 4",
+ "recipients": "texample-report@grafana.com",
+ "replyTo": "",
+ "message": "Hello, please, find the report attached",
+ "schedule": {
+ "startDate": "2022-10-02T10:00:00+02:00",
+ "endDate": "2022-11-02T20:00:00+02:00",
+ "frequency": "daily",
+ "intervalFrequency": "",
+ "intervalAmount": 0,
+ "workdaysOnly": true,
+ "timeZone": "Europe/Warsaw"
+ },
+ "options": {
+ "orientation": "landscape",
+ "layout": "grid"
+ },
+ "enableDashboardUrl": true,
+ "dashboards": [
+ {
+ "dashboard": {
+ "uid": "7MeksYbmk",
+ },
+ "timeRange": {
+ "from": "2022-08-08T15:00:00+02:00",
+ "to": "2022-09-02T17:00:00+02:00"
+ },
+ "reportVariables": {
+ "varibale1": "Value1"
+ }
+ }
+ ],
+ "formats": [
+ "pdf",
+ "csv"
+ ]
+}
+```
+
+#### Config JSON Body Schema
+
+| Field name | Data type | Description |
+| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| name | string | Name of the report that is used as an email subject. |
+| recipients | string | Comma-separated list of emails to which to send the report to. |
+| replyTo | string | Comma-separated list of emails used in a reply-to field of the report email. |
+| message | string | Text message used for the body of the report email. |
+| startDate | string | Report distribution starts from this date. |
+| endDate | string | Report distribution ends on this date. |
+| frequency | string | Specifies how often the report should be sent. Can be `once`, `hourly`, `daily`, `weekly`, `monthly`, `last` or `custom`.
`last` - schedules the report for the last day of month.
`custom` - schedules the report to be sent on a custom interval. It requires `intervalFrequency` and `intervalAmount` to be specified: for example, every 2 weeks, where 2 is an `intervalAmount` and `weeks` is an `intervalFrequency`. |
+| intervalFrequency | string | The type of the `custom` interval: `hours`, `days`, `weeks`, `months`. |
+| intervalAmount | number | `custom` interval amount. |
+| workdaysOnly | string | Send the report only on Monday-Friday. Applicable to `hourly` and `daily` types of schedule. |
+| timeZone | string | Time zone used to schedule report execution. |
+| orientation | string | Can be `portrait` or `landscape`. |
+| layout | string | Can be `grid` or `simple`. |
+| enableDashboardUrl | bool | Adds a dashboard url to the bottom of the report email. |
+| formats | []string | Specified what kind of attachment to generate for the report - `csv`, `pdf`, `image`. `pdf` is the default one. `csv` attaches a CSV file for each table panel. `image` embeds an image of a dashboard into the email's body. |
+| dashboards | []object | Dashboards to generate a report for. See "Report Dashboard Schema" section below. |
+
+#### Report Dashboard Schema
+
+| Field name | Data type | Description |
+| ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| dashboard.uid | string | Dashboard [UID](../dashboard#identifier-id-vs-unique-identifier-uid). |
+| timeRange.from | string | Dashboard time range from. |
+| timeRange.to | string | Dashboard time range to. |
+| reportVariables. | string | Key-value pairs containing the template variables for this report, in JSON format. If empty, the template variables from the report's dashboard will be used. |
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 35
+
+{
+ "id": 4,
+ "message": "Report created"
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **400** – Bad request (invalid json, missing or invalid fields values, etc.).
+- **403** - Forbidden (access denied to a report or a dashboard used in the report).
+- **500** - Unexpected error or server misconfiguration. Refer to server logs for more details
+
+## Update a report
+
+`PUT /api/reports/:id`
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| ------------- | --------------------------------------------------------- |
+| reports:write | reports:\*reports:id:\*reports:1(single report) |
+
+### Example request
+
+See [JSON body schema]({{< ref "#config-json-body-schema" >}}) for fields description.
+
+```http
+GET /api/reports HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+{
+ "name": "Updated Report",
+ "recipients": "example-report@grafana.com",
+ "replyTo": "",
+ "message": "Hello, please, find the report attached",
+ "schedule": {
+ "frequency": "hourly",
+ "timeZone": "Africa/Cairo",
+ "workdaysOnly": true,
+ "startDate": "2022-10-10T10:00:00+02:00",
+ "endDate": "2022-11-20T19:00:00+02:00"
+ },
+ "options": {
+ "orientation": "landscape",
+ "layout": "grid",
+ },
+ "enableDashboardUrl": true,
+ "state": "scheduled",
+ "dashboards": [
+ {
+ "dashboard": {
+ "id": 463,
+ "uid": "7MeksYbmk",
+ "name": "Alerting with TestData"
+ },
+ "timeRange": {
+ "from": "2022-08-08T15:00:00+02:00",
+ "to": "2022-09-02T17:00:00+02:00"
+ },
+ "reportVariables": {
+ "varibale1": "Value1"
+ }
+ }
+ ],
+ "formats": [
+ "pdf",
+ "csv"
+ ]
+}
+```
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 28
+
+{
+ "message": "Report updated"
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **400** – Bad request (invalid json, missing or invalid fields values, etc.).
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **403** – Forbidden (access denied to a report or a dashboard used in the report).
+- **404** – Not found (such report does not exist).
+- **500** – Unexpected error or server misconfiguration. Refer to server logs for more details.
+
+## Delete a report
+
+`DELETE /api/reports/:id`
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| -------------- | --------------------------------------------------------- |
+| reports:delete | reports:\*reports:id:\*reports:1(single report) |
+
+### Example request
+
+```http
+GET /api/reports/6 HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 39
+
+{
+ "message": "Report config was removed"
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **400** – Bad request (invalid report ID).
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **404** - Not found (report with this ID does not exist).
+- **500** - Unexpected error or server misconfiguration. Refer to server logs for more details
+
## Send a report
-> Only available in Grafana Enterprise v7.0+.
-
-> This API endpoint is experimental and may be deprecated in a future release. On deprecation, a migration strategy will be provided and the endpoint will remain functional until the next major release of Grafana.
-
`POST /api/reports/email`
Generate and send a report. This API waits for the report to be generated before returning. We recommend that you set the client's timeout to at least 60 seconds.
@@ -51,13 +451,13 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
}
```
-### JSON Body Schema
+#### JSON Body Schema
-| Field name | Data type | Description |
-| ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| id | string | ID of the report to send. It is the same as in the URL when editing a report, not to be confused with the ID of the dashboard. Required. |
-| emails | string | Comma-separated list of emails to which to send the report to. Overrides the emails from the report. Required if **useEmailsFromReport** is not present. |
-| useEmailsFromReport | boolean | Send the report to the emails specified in the report. Required if **emails** is not present. |
+| Field name | Data type | Description |
+| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| id | string | ID of the report to send. It is the same as in the URL when editing a report, not to be confused with the ID of the dashboard. Required. |
+| emails | string | Comma-separated list of emails to which to send the report to. Overrides the emails from the report. Required if `useEmailsFromReport` is not present. |
+| useEmailsFromReport | boolean | Send the report to the emails specified in the report. Required if `emails` is not present. |
### Example response
@@ -71,11 +471,205 @@ Content-Length: 29
### Status Codes
-| Code | Description |
-| ---- | ----------------------------------------------------------------------------------- |
-| 200 | Report was sent. |
-| 400 | Bad request (invalid json, missing content-type, missing or invalid fields, etc.). |
-| 401 | Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}). |
-| 403 | User is authenticated but is not authorized to generate the report. |
-| 404 | Report not found. |
-| 500 | Unexpected error or server misconfiguration. Refer to server logs for more details. |
+- **200** – Report was sent.
+- **400** – Bad request (invalid json, missing content-type, missing or invalid fields, etc.).
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **403** - Forbidden (access denied to a report or a dashboard used in the report).
+- **404** - Report not found.
+- **500** - Unexpected error or server misconfiguration. Refer to server logs for more details.
+
+## Get reports branding settings
+
+`GET /api/reports/settings`
+
+Returns reports branding settings that are global and used across all the reports.
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| --------------------- | ----- |
+| reports.settings:read | n/a |
+
+### Example request
+
+```http
+GET /api/reports/settings HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 181
+
+{
+ "id": 1,
+ "userId": 1,
+ "orgId": 1,
+ "branding": {
+ "reportLogoUrl": "",
+ "emailLogoUrl": "",
+ "emailFooterMode": "sent-by",
+ "emailFooterText": "Grafana Labs",
+ "emailFooterLink": "https://grafana.com/"
+ }
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **500** - Unexpected error or server misconfiguration. Refer to server logs for more detail
+
+## Save reports branding settings
+
+`POST /api/reports/settings`
+
+Creates settings if they don't exist, otherwise updates them. These settings are global and used across all the reports.
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| ---------------------- | ----- |
+| reports.settings:write | n/a |
+
+### Example request
+
+```http
+POST /api/reports/settings HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+{
+ "branding": {
+ "reportLogoUrl": "https://grafana.com/reportLogo.jpg",
+ "emailLogoUrl": "https://grafana.com/emailLogo.jpg",
+ "emailFooterMode": "sent-by",
+ "emailFooterText": "Grafana Labs",
+ "emailFooterLink": "https://grafana.com/"
+ }
+}
+```
+
+#### JSON Body Schema
+
+| Field name | Data type | Description |
+| ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| branding.reportLogoUrl | string | URL of an image used as a logo on every page of the report. |
+| branding.emailLogoUrl | string | URL of an image used as a logo in the email. |
+| branding.emailFooterMode | string | Can be `sent-by` or `none`. `sent-by` adds a "Sent by `branding.emailFooterText`" footer link to the email. Requires specifying values in the `branding.emailFooterText` and `branding.emailFooterLink` fields. `none` suppresses adding a "Sent by" footer link to the email. |
+| branding.emailFooterText | string | Text of a URL added to the email "Sent by" footer. |
+| branding.emailFooterLink | string | URL address value added to the email "Sent by" footer. |
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 35
+
+{
+ "message": "Report settings saved"
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **400** – Bad request (invalid json, missing or invalid fields values, etc.).
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **500** - Unexpected error or server misconfiguration. Refer to server logs for more detail
+
+## Send a test email
+
+`POST /api/reports/test-email`
+
+Sends a test email with a report without persisting it in the database.
+
+#### Required permissions
+
+See note in the [introduction]({{< ref "#reporting-api" >}}) for an explanation.
+
+| Action | Scope |
+| ------------ | ----- |
+| reports:send | n/a |
+
+### Example request
+
+See [JSON body schema]({{< ref "#config-json-body-schema" >}}) for fields description.
+
+```http
+POST /api/reports/test-email HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+{{
+ "name": "Report 4",
+ "recipients": "example-report@grafana.com",
+ "replyTo": "",
+ "message": "Hello, please, find the report attached",
+ "schedule": {
+ "startDate": "2022-10-02T10:00:00+02:00",
+ "endDate": "2022-11-02T20:00:00+02:00",
+ "frequency": "daily",
+ "intervalFrequency": "",
+ "intervalAmount": 0,
+ "workdaysOnly": true,
+ "timeZone": "Europe/Warsaw"
+ },
+ "options": {
+ "orientation": "landscape",
+ "layout": "grid"
+ },
+ "enableDashboardUrl": true,
+ "dashboards": [
+ {
+ "dashboard": {
+ "uid": "7MeksYbmk",
+ },
+ "timeRange": {
+ "from": "2022-08-08T15:00:00+02:00",
+ "to": "2022-09-02T17:00:00+02:00"
+ },
+ "reportVariables": {
+ "varibale1": "Value1"
+ }
+ }
+ ],
+ "formats": [
+ "pdf",
+ "csv"
+ ]
+}
+```
+
+### Example response
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 29
+
+{
+ "message": "Test email sent"
+}
+```
+
+### Status Codes
+
+- **200** – OK
+- **400** – Bad request (invalid json, missing or invalid fields values, etc.).
+- **401** - Authentication failed, refer to [Authentication API]({{< relref "auth/" >}}).
+- **403** - Forbidden (access denied to a report or a dashboard used in the report).
+- **500** - Unexpected error or server misconfiguration. Refer to server logs for more details
diff --git a/docs/sources/developers/plugins/migration-guide.md b/docs/sources/developers/plugins/migration-guide.md
index 8b525640f69..8269d182272 100644
--- a/docs/sources/developers/plugins/migration-guide.md
+++ b/docs/sources/developers/plugins/migration-guide.md
@@ -17,6 +17,8 @@ This guide helps you identify the steps required to update a plugin from the Gra
- [Plugin migration guide](#plugin-migration-guide)
- [Introduction](#introduction)
- [Table of contents](#table-of-contents)
+ - [From version 9.1.x to 9.2.x](#from-version-91x-to-92x)
+ - [NavModelItem requires a valid icon name](#navmodelitem-requires-a-valid-icon-name)
- [From version 8.x to 9.x](#from-version-8x-to-9x)
- [9.0 breaking changes](#90-breaking-changes)
- [theme.visualization.getColorByName replaces getColorForTheme](#themevisualizationgetcolorbyname-replaces-getcolorfortheme)
@@ -60,6 +62,32 @@ This guide helps you identify the steps required to update a plugin from the Gra
- [Migrate to data frames](#migrate-to-data-frames)
- [Troubleshoot plugin migration](#troubleshoot-plugin-migration)
+## From version 9.1.x to 9.2.x
+
+### NavModelItem requires a valid icon name
+
+The typings of the `NavModelItem` have improved to only allow a valid `IconName` for the icon property. You can find the complete list of valid icons [here](https://github.com/grafana/grafana/blob/v9.2.0-beta1/packages/grafana-data/src/types/icon.ts). The icons specified in the list will work for older versions of Grafana 9.
+
+Example:
+
+```ts
+// before
+const model: NavModelItem = {
+ id: 'settings',
+ text: 'Settings',
+ icon: 'fa fa-cog',
+ url: `${baseUrl}/settings`,
+};
+
+// after
+const model: NavModelItem = {
+ id: 'settings',
+ text: 'Settings',
+ icon: 'cog',
+ url: `${baseUrl}/settings`,
+};
+```
+
## From version 8.x to 9.x
### 9.0 breaking changes
diff --git a/docs/sources/panels/configure-standard-options/index.md b/docs/sources/panels/configure-standard-options/index.md
index 587d297362b..ca7da3050dc 100644
--- a/docs/sources/panels/configure-standard-options/index.md
+++ b/docs/sources/panels/configure-standard-options/index.md
@@ -54,6 +54,7 @@ You can use the unit dropdown to also specify custom units, custom prefix or suf
To select a custom unit enter the unit and select the last `Custom: xxx` option in the dropdown.
- `suffix:` for custom unit that should go after value.
+- `prefix:` for custom unit that should go before value.
- `time:` For custom date time formats type for example `time:YYYY-MM-DD`. See [formats](https://momentjs.com/docs/#/displaying/) for the format syntax and options.
- `si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the
source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that
diff --git a/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md b/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md
index 4e760a47c3f..5b4535fe089 100644
--- a/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md
+++ b/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md
@@ -114,7 +114,7 @@ is_nan takes a number or a series and returns `1` for `NaN` values and `0` for o
##### is_null
-is_nan takes a number or a series and returns `1` for `null` values and `0` for other values. For example `is_null($A)`.
+is_null takes a number or a series and returns `1` for `null` values and `0` for other values. For example `is_null($A)`.
##### is_number
diff --git a/e2e/various-suite/loki-query-builder.spec.ts b/e2e/various-suite/loki-query-builder.spec.ts
index 32ee27e0d5e..6557a993c4c 100644
--- a/e2e/various-suite/loki-query-builder.spec.ts
+++ b/e2e/various-suite/loki-query-builder.spec.ts
@@ -14,6 +14,8 @@ const addDataSource = () => {
});
};
+const finalQuery = 'rate({instance=~"instance1|instance2"} | logfmt | __error__=`` [$__interval]';
+
describe('Loki query builder', () => {
beforeEach(() => {
e2e.flows.login('admin', 'admin');
@@ -37,8 +39,6 @@ describe('Loki query builder', () => {
req.reply({ status: 'success', data: [{ instance: 'instance1' }] });
});
- const finalQuery = 'rate({instance=~"instance1|instance2"} | logfmt | __error__=`` [$__interval]';
-
// Go to Explore and choose Loki data source
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
@@ -72,13 +72,21 @@ describe('Loki query builder', () => {
e2e().contains(MISSING_LABEL_FILTER_ERROR_MESSAGE).should('not.exist');
e2e().contains(finalQuery).should('be.visible');
- // Switch to code editor and check if query was parsed
- for (const word of finalQuery.split(' ')) {
- e2e().contains(word).should('be.visible');
- }
+ // Toggle raw query
+ e2e().contains('label', 'Raw query').click();
+ e2e().contains('Raw query').should('have.length', 1);
- // Switch to explain mode and check if query is visible
+ // Change to code editor
+ e2e().contains('label', 'Code').click();
+ // We need to test this manually because the final query is split into separate DOM elements using e2e().contains(finalQuery).should('be.visible'); does not detect the query.
+ e2e().contains('rate').should('be.visible');
+ e2e().contains('instance1|instance2').should('be.visible');
+ e2e().contains('logfmt').should('be.visible');
+ e2e().contains('__error__').should('be.visible');
+ e2e().contains('$__interval').should('be.visible');
+
+ // Checks the explain mode toggle
e2e().contains('label', 'Explain').click();
- e2e().contains(finalQuery).should('be.visible');
+ e2e().contains('Fetch all log lines matching label filters.').should('be.visible');
});
});
diff --git a/go.mod b/go.mod
index 92fd45c35d6..46db1203e84 100644
--- a/go.mod
+++ b/go.mod
@@ -55,7 +55,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/gosimple/slug v1.12.0
github.com/grafana/cuetsy v0.1.1
- github.com/grafana/grafana-aws-sdk v0.10.8
+ github.com/grafana/grafana-aws-sdk v0.11.0
github.com/grafana/grafana-azure-sdk-go v1.3.0
github.com/grafana/grafana-plugin-sdk-go v0.139.0
github.com/grafana/thema v0.0.0-20220817114012-ebeee841c104
@@ -106,7 +106,7 @@ require (
go.opentelemetry.io/otel/trace v1.6.3
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
- golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
+ golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
@@ -231,7 +231,7 @@ require (
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0
go.uber.org/goleak v1.1.12 // indirect
- golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
+ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/text v0.3.7
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
google.golang.org/appengine v1.6.7 // indirect
@@ -254,6 +254,7 @@ require (
github.com/google/go-github/v45 v45.2.0
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f
github.com/jmoiron/sqlx v1.3.5
+ github.com/matryer/is v1.4.0
github.com/urfave/cli v1.22.5
go.etcd.io/etcd/api/v3 v3.5.4
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.31.0
diff --git a/go.sum b/go.sum
index 272fd7e2903..519b3771fb6 100644
--- a/go.sum
+++ b/go.sum
@@ -1374,8 +1374,8 @@ github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f h1:FvvSVEbnGeM2bUivG
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f/go.mod h1:uPG2nyK4CtgNDmWv7qyzYcdI+S90kHHRWvHnBtEMBXM=
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA=
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
-github.com/grafana/grafana-aws-sdk v0.10.8 h1:6MGlWlQD4E0aI+Vp4Cfgzsj9V3U7kSQ1wCye9D1NMoU=
-github.com/grafana/grafana-aws-sdk v0.10.8/go.mod h1:5Iw3xY7iXJfNaYHrRHMXa/kaB2lWoyntg71PPLGvSs8=
+github.com/grafana/grafana-aws-sdk v0.11.0 h1:ncPD/UN0wNcKq3kEU90RdvrnK/6R4VW2Lo5dPcGk9t0=
+github.com/grafana/grafana-aws-sdk v0.11.0/go.mod h1:5Iw3xY7iXJfNaYHrRHMXa/kaB2lWoyntg71PPLGvSs8=
github.com/grafana/grafana-azure-sdk-go v1.3.0 h1:zboQpq/ljBjqHo/6UQNZAUwqGTtnEGRYSEnqIQvLuAo=
github.com/grafana/grafana-azure-sdk-go v1.3.0/go.mod h1:rgrnK9m6CgKlgx4rH3FFP/6dTdyRO6LYC2mVZov35yo=
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2ud7NNM7LrGPO4x0NFR8qLq68CqI4SmB7I2yRN2w9oE=
@@ -2881,8 +2881,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
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-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -3097,8 +3097,8 @@ golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/package.json b/package.json
index 2df90d27fa5..f9e8e785985 100644
--- a/package.json
+++ b/package.json
@@ -173,6 +173,7 @@
"babel-loader": "8.2.5",
"babel-plugin-angularjs-annotate": "0.10.0",
"babel-plugin-macros": "3.1.0",
+ "blob-polyfill": "7.0.20220408",
"copy-webpack-plugin": "9.0.1",
"css-loader": "6.7.1",
"css-minimizer-webpack-plugin": "4.1.0",
@@ -257,7 +258,7 @@
"@grafana/e2e-selectors": "workspace:*",
"@grafana/experimental": "^0.0.2-canary.36",
"@grafana/google-sdk": "0.0.3",
- "@grafana/lezer-logql": "0.1.0",
+ "@grafana/lezer-logql": "0.1.1",
"@grafana/monaco-logql": "^0.0.6",
"@grafana/runtime": "workspace:*",
"@grafana/schema": "workspace:*",
@@ -354,7 +355,7 @@
"rc-drawer": "4.4.3",
"rc-slider": "9.7.5",
"rc-time-picker": "3.7.3",
- "rc-tree": "5.6.6",
+ "rc-tree": "5.7.0",
"re-resizable": "6.9.9",
"react": "17.0.2",
"react-awesome-query-builder": "5.3.1",
@@ -408,7 +409,7 @@
"resolutions": {
"underscore": "1.13.4",
"@types/slate": "0.47.9",
- "@rushstack/node-core-library": "3.52.0",
+ "@rushstack/node-core-library": "3.53.0",
"@rushstack/rig-package": "0.3.13",
"@rushstack/ts-command-line": "4.12.1",
"@storybook/react/webpack": "5.74.0",
diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json
index e2d293a0fde..4e197b6fe70 100644
--- a/packages/grafana-data/package.json
+++ b/packages/grafana-data/package.json
@@ -23,7 +23,8 @@
},
"files": [
"dist",
- "CHANGELOG.md",
+ "./README.md",
+ "./CHANGELOG.md",
"LICENSE_APACHE2"
],
"scripts": {
diff --git a/packages/grafana-data/src/utils/logs.ts b/packages/grafana-data/src/utils/logs.ts
index 35bd1430d7e..93993a0178e 100644
--- a/packages/grafana-data/src/utils/logs.ts
+++ b/packages/grafana-data/src/utils/logs.ts
@@ -16,6 +16,7 @@ const LOGFMT_REGEXP = /(?:^|\s)([\w\(\)\[\]\{\}]+)=(""|(?:".*?[^\\]"|[^"\s]\S*))
*
* Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn`
*/
+/** @deprecated will be removed in the next major version */
export function getLogLevel(line: string): LogLevel {
if (!line) {
return LogLevel.unknown;
@@ -37,6 +38,7 @@ export function getLogLevel(line: string): LogLevel {
return level;
}
+/** @deprecated will be removed in the next major version */
export function getLogLevelFromKey(key: string | number): LogLevel {
const level = (LogLevel as any)[key.toString().toLowerCase()];
if (level) {
@@ -46,6 +48,7 @@ export function getLogLevelFromKey(key: string | number): LogLevel {
return LogLevel.unknown;
}
+/** @deprecated will be removed in the next major version */
export function addLogLevelToSeries(series: DataFrame, lineIndex: number): DataFrame {
const levels = new ArrayVector();
const lines = series.fields[lineIndex];
@@ -68,6 +71,7 @@ export function addLogLevelToSeries(series: DataFrame, lineIndex: number): DataF
};
}
+/** @deprecated will be removed in the next major version */
export const LogsParsers: { [name: string]: LogsParser } = {
JSON: {
buildMatcher: (label) => new RegExp(`(?:{|,)\\s*"${label}"\\s*:\\s*"?([\\d\\.]+|[^"]*)"?`),
@@ -109,6 +113,7 @@ export const LogsParsers: { [name: string]: LogsParser } = {
},
};
+/** @deprecated will be removed in the next major version */
export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): LogLabelStatsModel[] {
// Consider only rows that satisfy the matcher
const rowsWithField = rows.filter((row) => extractor.test(row.entry));
@@ -124,6 +129,7 @@ export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): Log
return getSortedCounts(countsByValue, rowCount);
}
+/** @deprecated will be removed in the next major version */
export function calculateLogsLabelStats(rows: LogRowModel[], label: string): LogLabelStatsModel[] {
// Consider only rows that have the given label
const rowsWithLabel = rows.filter((row) => row.labels[label] !== undefined);
@@ -134,6 +140,7 @@ export function calculateLogsLabelStats(rows: LogRowModel[], label: string): Log
return getSortedCounts(countsByValue, rowCount);
}
+/** @deprecated will be removed in the next major version */
export function calculateStats(values: unknown[]): LogLabelStatsModel[] {
const nonEmptyValues = values.filter((value) => value !== undefined && value !== null);
const countsByValue = countBy(nonEmptyValues);
@@ -148,6 +155,7 @@ const getSortedCounts = (countsByValue: { [value: string]: number }, rowCount: n
.value();
};
+/** @deprecated will be removed in the next major version */
export function getParser(line: string): LogsParser | undefined {
let parser;
try {
@@ -163,6 +171,7 @@ export function getParser(line: string): LogsParser | undefined {
return parser;
}
+/** @deprecated will be removed in the next major version */
export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
// compare milliseconds
if (a.timeEpochMs < b.timeEpochMs) {
@@ -185,6 +194,7 @@ export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
return 0;
};
+/** @deprecated will be removed in the next major version */
export const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
// compare milliseconds
if (a.timeEpochMs > b.timeEpochMs) {
@@ -207,15 +217,18 @@ export const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
return 0;
};
+/** @deprecated will be removed in the next major version */
export const sortLogsResult = (logsResult: LogsModel | null, sortOrder: LogsSortOrder): LogsModel => {
const rows = logsResult ? sortLogRows(logsResult.rows, sortOrder) : [];
return logsResult ? { ...logsResult, rows } : { hasUniqueLabels: false, rows };
};
+/** @deprecated will be removed in the next major version */
export const sortLogRows = (logRows: LogRowModel[], sortOrder: LogsSortOrder) =>
sortOrder === LogsSortOrder.Ascending ? logRows.sort(sortInAscendingOrder) : logRows.sort(sortInDescendingOrder);
// Currently supports only error condition in Loki logs
+/** @deprecated will be removed in the next major version */
export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorMessage?: string } => {
if (logRow.labels.__error__) {
return {
@@ -228,5 +241,6 @@ export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorM
};
};
+/** @deprecated will be removed in the next major version */
export const escapeUnescapedString = (string: string) =>
string.replace(/\\r\\n|\\n|\\t|\\r/g, (match: string) => (match.slice(1) === 't' ? '\t' : '\n'));
diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json
index 9c71a7bbc92..45b1de30667 100644
--- a/packages/grafana-e2e-selectors/package.json
+++ b/packages/grafana-e2e-selectors/package.json
@@ -26,7 +26,8 @@
},
"files": [
"dist",
- "CHANGELOG.md",
+ "./README.md",
+ "./CHANGELOG.md",
"LICENSE_APACHE2"
],
"scripts": {
diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json
index e94f37a8481..bdbfa91a4e4 100644
--- a/packages/grafana-e2e/package.json
+++ b/packages/grafana-e2e/package.json
@@ -30,7 +30,8 @@
"dist",
"cli.js",
"cypress.json",
- "CHANGELOG.md",
+ "./README.md",
+ "./CHANGELOG.md",
"LICENSE_APACHE2"
],
"scripts": {
diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json
index ba5bee319c2..73f557c6965 100644
--- a/packages/grafana-runtime/package.json
+++ b/packages/grafana-runtime/package.json
@@ -24,7 +24,8 @@
},
"files": [
"dist",
- "CHANGELOG.md",
+ "./README.md",
+ "./CHANGELOG.md",
"LICENSE_APACHE2"
],
"scripts": {
diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json
index 66c9ac3206f..fe87ef1f45c 100644
--- a/packages/grafana-schema/package.json
+++ b/packages/grafana-schema/package.json
@@ -23,7 +23,8 @@
},
"files": [
"dist",
- "CHANGELOG.md",
+ "./README.md",
+ "./CHANGELOG.md",
"LICENSE_APACHE2"
],
"scripts": {
diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts
index a34df96996a..4ec9a38b62f 100644
--- a/packages/grafana-schema/src/index.gen.ts
+++ b/packages/grafana-schema/src/index.gen.ts
@@ -74,3 +74,9 @@ export {
defaultFieldConfigSource,
defaultFieldConfig
} from './veneer/dashboard.types';
+
+// Raw generated types from playlist entity type.
+export type { Playlist } from './raw/playlist/x/playlist.gen';
+
+// Raw generated default consts from playlist entity type.
+export { defaultPlaylist } from './raw/playlist/x/playlist.gen';
diff --git a/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts b/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts
new file mode 100644
index 00000000000..bfd1c192b1b
--- /dev/null
+++ b/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts
@@ -0,0 +1,69 @@
+// This file is autogenerated. DO NOT EDIT.
+//
+// Generated by pkg/framework/coremodel/gen.go
+//
+// Derived from the Thema lineage declared in pkg/coremodel/playlist/coremodel.cue
+//
+// Run `make gen-cue` from repository root to regenerate.
+
+export interface Playlist {
+ /**
+ * Unique playlist identifier for internal use, set by Grafana.
+ */
+ id: number;
+ /**
+ * Interval sets the time between switching views in a playlist.
+ * FIXME: Is this based on a standardized format or what options are available? Can datemath be used?
+ */
+ interval: string;
+ /**
+ * The ordered list of items that the playlist will iterate over.
+ */
+ items?: Array<{
+ /**
+ * FIXME: The prefixDropper removes playlist from playlist_id, that doesn't work for us since it'll mean we'll have Id twice.
+ * ID of the playlist item for internal use by Grafana. Deprecated.
+ */
+ id: number;
+ /**
+ * PlaylistID for the playlist containing the item. Deprecated.
+ */
+ playlistid: number;
+ /**
+ * Type of the item.
+ */
+ type: ('dashboard_by_uid' | 'dashboard_by_id' | 'dashboard_by_tag');
+ /**
+ * Value depends on type and describes the playlist item.
+ *
+ * - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This
+ * is not portable as the numerical identifier is non-deterministic between different instances.
+ * Will be replaced by dashboard_by_uid in the future.
+ * - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All
+ * dashboards behind the tag will be added to the playlist.
+ */
+ value: string;
+ /**
+ * Title is the human-readable identifier for the playlist item.
+ */
+ title: string;
+ /**
+ * Order is the position in the list for the item. Deprecated.
+ */
+ order: number;
+ }>;
+ /**
+ * Name of the playlist.
+ */
+ name: string;
+ /**
+ * Unique playlist identifier. Generated on creation, either by the
+ * creator of the playlist of by the application.
+ */
+ uid: string;
+}
+
+export const defaultPlaylist: Partial = {
+ interval: '5m',
+ items: [],
+};
diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json
index f75a0e503a7..b67d92b072b 100644
--- a/packages/grafana-toolkit/package.json
+++ b/packages/grafana-toolkit/package.json
@@ -19,25 +19,26 @@
"grafana-toolkit": "./bin/grafana-toolkit.js"
},
"publishConfig": {
- "bin": {
- "grafana-toolkit": "./dist/bin/grafana-toolkit.js"
- },
"access": "public"
},
"files": [
- "dist",
- "README.md",
- "CHANGELOG.md"
+ "config",
+ "src",
+ "sass",
+ "./README.md",
+ "./CHANGELOG.md",
+ "LICENSE_APACHE2"
],
"scripts": {
"build": "grafana-toolkit toolkit:build",
- "clean": "rimraf ./dist ./compiled ./package.tgz",
- "precommit": "npm run lint & npm run typecheck",
+ "clean": "rimraf ./dist ./compiled ./sass ./package.tgz",
+ "prepack": "mv ./src ./src_bak && cp -r ./dist/src ./src",
+ "postpack": "rimraf ./src && mv ./src_bak ./src",
"typecheck": "tsc --noEmit"
},
"main": "src/index.ts",
"dependencies": {
- "@babel/core": "^7.18.9",
+ "@babel/core": "7.18.9",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-object-rest-spread": "7.18.9",
@@ -46,11 +47,11 @@
"@babel/plugin-transform-react-constant-elements": "7.18.9",
"@babel/plugin-transform-runtime": "7.18.10",
"@babel/plugin-transform-typescript": "7.19.0",
- "@babel/preset-env": "^7.18.9",
- "@babel/preset-react": "^7.18.6",
- "@babel/preset-typescript": "^7.18.6",
+ "@babel/preset-env": "7.18.9",
+ "@babel/preset-react": "7.18.6",
+ "@babel/preset-typescript": "7.18.6",
"@grafana/data": "9.3.0-pre",
- "@grafana/eslint-config": "^4.0.0",
+ "@grafana/eslint-config": "5.0.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@grafana/ui": "9.3.0-pre",
"@jest/core": "27.5.1",
diff --git a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts
index ae80a8a6645..2711425b2a4 100644
--- a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts
+++ b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts
@@ -24,8 +24,6 @@ const compile = () =>
const copyFiles = () => {
const files = [
- 'config/circleci/config.yml',
- 'bin/grafana-toolkit.js',
'src/config/prettier.plugin.config.json',
'src/config/prettier.plugin.rc.js',
'src/config/tsconfig.plugin.json',
@@ -60,12 +58,16 @@ const copyFiles = () => {
const copySassFiles = () => {
const files = ['_variables.generated.scss', '_variables.dark.generated.scss', '_variables.light.generated.scss'];
+ const exportDir = `${cwd}/sass`;
return useSpinner(`Copy scss files ${files.join(', ')} files`, async () => {
const sassDir = path.resolve(cwd, '../../public/sass/');
+ if (!fs.existsSync(exportDir)) {
+ fs.mkdirSync(exportDir);
+ }
const promises = files.map((file) => {
return new Promise((resolve, reject) => {
const name = file.replace('.generated', '');
- fs.copyFile(`${sassDir}/${file}`, `${distDir}/sass/${name}`, (err) => {
+ fs.copyFile(`${sassDir}/${file}`, `${exportDir}/${name}`, (err) => {
if (err) {
reject(err);
return;
@@ -89,8 +91,6 @@ const toolkitBuildTaskRunner: TaskRunner = async () => {
await clean();
await compile();
- fs.mkdirSync('./dist/bin');
- fs.mkdirSync('./dist/sass');
await copyFiles();
await copySassFiles();
};
diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json
index a79f602719d..29dd855df5a 100644
--- a/packages/grafana-ui/package.json
+++ b/packages/grafana-ui/package.json
@@ -26,7 +26,8 @@
},
"files": [
"dist",
- "CHANGELOG.md",
+ "./README.md",
+ "./CHANGELOG.md",
"LICENSE_APACHE2"
],
"scripts": {
@@ -79,7 +80,7 @@
"rc-slider": "9.7.5",
"rc-time-picker": "^3.7.3",
"react-beautiful-dnd": "13.1.0",
- "react-calendar": "3.7.0",
+ "react-calendar": "3.9.0",
"react-colorful": "5.5.1",
"react-custom-scrollbars-2": "4.5.0",
"react-dropzone": "14.2.2",
diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx
index 8139228a81c..0251fbcfd0c 100644
--- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx
+++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx
@@ -1,4 +1,4 @@
-import { css } from '@emotion/css';
+import { css, cx } from '@emotion/css';
import React, { useEffect, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
@@ -25,6 +25,8 @@ export interface ConfirmModalProps {
dismissText?: string;
/** Icon for the modal header */
icon?: IconName;
+ /** Additional styling for modal container */
+ modalClass?: string;
/** Text user needs to fill in before confirming */
confirmationText?: string;
/** Text for alternative button */
@@ -46,6 +48,7 @@ export const ConfirmModal = ({
confirmationText,
dismissText = 'Cancel',
alternativeText,
+ modalClass,
icon = 'exclamation-triangle',
onConfirm,
onDismiss,
@@ -66,7 +69,7 @@ export const ConfirmModal = ({
}, [isOpen]);
return (
-
+
+ <>
+ {/* tabIndex={0} is needed for keyboard accessibility in the plot area */}
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
+