Merge branch 'main' into ash/83180
This commit is contained in:
@@ -4581,10 +4581,6 @@ exports[`better eslint`] = {
|
||||
"public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
|
||||
@@ -68,6 +68,5 @@ To create recording rules, follow these steps.
|
||||
[annotation-label]: "/docs/grafana/ -> /docs/grafana/<GRAFANA_VERSION>/alerting/fundamentals/annotation-label"
|
||||
[annotation-label]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/annotation-label"
|
||||
|
||||
[configure-grafana]: "/docs/grafana/ -> /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana"
|
||||
[configure-grafana]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana"
|
||||
[configure-grafana]: "/docs/ -> /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana"
|
||||
{{% /docs/reference %}}
|
||||
|
||||
@@ -39,13 +39,13 @@ Refer to the table at the end of this page for a list of contact points and thei
|
||||
|
||||
3. You should use a cloud storage service unless sending alerts to Discord, Email, Pushover, Slack or Telegram. These integrations support either embedding screenshots in the email or attaching screenshots to the notification, while other integrations must link screenshots uploaded to a cloud storage bucket. If a cloud storage service has been configured then integrations that support both will link screenshots from the cloud storage bucket instead of embedding or attaching screenshots to the notification.
|
||||
|
||||
4. If uploading screenshots to a cloud storage service such as Amazon S3, Azure Blob Storage or Google Cloud Storage; and accessing screenshots in the bucket requires authentication, logging into a VPN or corporate network; then image previews might not work in all instant messaging and communication platforms as some services rewrite URLs to use their CDN. If this happens we recommend using [integrations which support uploading images]({{< relref "#supported-contact-points" >}}) or [disabling images in notifications]({{< relref "#configuration" >}}) altogether.
|
||||
4. If uploading screenshots to a cloud storage service such as Amazon S3, Azure Blob Storage or Google Cloud Storage; and accessing screenshots in the bucket requires authentication, logging into a VPN or corporate network; then image previews might not work in all instant messaging and communication platforms as some services rewrite URLs to use their CDN. If this happens we recommend using [integrations which support uploading images](#supported-contact-points) or [disabling images in notifications](#configuration) altogether.
|
||||
|
||||
5. When uploading screenshots to a cloud storage service Grafana uses a random 20 character (30 characters for Azure Blob Storage) filename for each image. This makes URLs hard to guess but not impossible.
|
||||
|
||||
6. Grafana does not delete screenshots from cloud storage. We recommend configuring a retention policy with your cloud storage service to delete screenshots older than 1 month.
|
||||
|
||||
7. If Grafana is configured to upload screenshots to its internal web server, and accessing Grafana requires logging into a VPN or corporate network; image previews might not work in all instant messaging and communication platforms as some services rewrite URLs to use their CDN. If this happens we recommend using [integrations which support uploading images]({{< relref "#supported-contact-points" >}}) or [disabling images in notifications]({{< relref "#configuration" >}}) altogether.
|
||||
7. If Grafana is configured to upload screenshots to its internal web server, and accessing Grafana requires logging into a VPN or corporate network; image previews might not work in all instant messaging and communication platforms as some services rewrite URLs to use their CDN. If this happens we recommend using [integrations which support uploading images](#supported-contact-points) or [disabling images in notifications](#configuration) altogether.
|
||||
|
||||
8. Grafana does not delete screenshots uploaded to its internal web server. To delete screenshots from `static_root_path/images/attachments` after a certain amount of time we recommend setting up a CRON job.
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ In text/template, templates start with `{{` and end with `}}` irrespective of wh
|
||||
|
||||
## Print
|
||||
|
||||
To print the value of something use `{{` and `}}`. You can print the value of dot, a field of dot, the result of a function, and the value of a [variable]({{< relref "#variables" >}}). For example, to print the `Alerts` field where dot refers to `ExtendedData` you would write the following:
|
||||
To print the value of something use `{{` and `}}`. You can print the value of dot, a field of dot, the result of a function, and the value of a [variable](#variables). For example, to print the `Alerts` field where dot refers to `ExtendedData` you would write the following:
|
||||
|
||||
```
|
||||
{{ .Alerts }}
|
||||
|
||||
@@ -15,7 +15,7 @@ weight: 150
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
Legacy alerting will be removed in Grafana v11.0.0. We recommend that you upgrade to Grafana Alerting as soon as possible.
|
||||
For more information, refer to [Legacy alerting deprecation]({{< relref "./legacy-alerting-deprecation" >}}).
|
||||
For more information, refer to [Legacy alerting deprecation](/docs/grafana/<GRAFANA_VERSION>/alerting/set-up/migrating-alerts/legacy-alerting-deprecation).
|
||||
{{% /admonition %}}
|
||||
|
||||
Grafana provides two methods for a seamless automatic upgrade of legacy alert rules and notification channels to Grafana Alerting:
|
||||
@@ -24,7 +24,7 @@ Grafana provides two methods for a seamless automatic upgrade of legacy alert ru
|
||||
2. **Simple Upgrade**: One-step upgrade method for specific needs where a preview environment is not essential.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
When upgrading with either method, your legacy dashboard alerts and notification channels are copied to a new format. This is non-destructive and can be [rolled back easily]({{< relref "#rolling-back-to-legacy-alerting" >}}).
|
||||
When upgrading with either method, your legacy dashboard alerts and notification channels are copied to a new format. This is non-destructive and can be [rolled back easily](#rolling-back-to-legacy-alerting).
|
||||
{{% /admonition %}}
|
||||
|
||||
## Key Considerations
|
||||
@@ -46,7 +46,7 @@ When upgrading with either method, your legacy dashboard alerts and notification
|
||||
|
||||
- Grafana `v10.3.0 or later`.
|
||||
- Grafana administrator access.
|
||||
- Enable `alertingPreviewUpgrade` [feature toggle]({{< relref "../../../setup-grafana/configure-grafana/feature-toggles" >}}) (enabled by default in v10.4.0 or later).
|
||||
- Enable `alertingPreviewUpgrade` [feature toggle][feature-toggles] (enabled by default in v10.4.0 or later).
|
||||
|
||||
### Suited for
|
||||
|
||||
@@ -83,7 +83,7 @@ Alerts generated by the new alerting system are visible in the **Alerting** sect
|
||||
- **Export upgraded resources**: If you use provisioning methods to manage alert rules and notification channels, you can export the upgraded versions to generate provisioning files compatible with Grafana Alerting.
|
||||
- **Test new provisioning definitions**: Ensure your as-code setup aligns with the new system before completing the upgrade process. Both legacy and Grafana Alerting alerts can be provisioned simultaneously to facilitate a smooth transition.
|
||||
1. **Finalize the Upgrade**:
|
||||
- **Contact your Grafana server administrator**: Once you're confident in the state of your previewed upgrade, request to [enable Grafana Alerting]({{< relref "#enable-grafana-alerting" >}}).
|
||||
- **Contact your Grafana server administrator**: Once you're confident in the state of your previewed upgrade, request to [enable Grafana Alerting](#enable-grafana-alerting).
|
||||
- **Continued use for upgraded organizations**: Organizations that have already completed the preview upgrade will seamlessly continue using their configured setup.
|
||||
- **Automatic upgrade for others**: Organizations that haven't initiated the upgrade with preview process will undergo the traditional automatic upgrade during this restart.
|
||||
- **Address issues before restart**: Exercise caution, as Grafana will not start if any traditional automatic upgrades encounter errors. Ensure all potential issues are resolved before initiating this step.
|
||||
@@ -111,11 +111,11 @@ Once Grafana Alerting is enabled, you can review and adjust your upgraded alerts
|
||||
### To perform the simple upgrade, complete the following steps.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
Any errors encountered during the upgrade process will fail the upgrade and prevent Grafana from starting. If this occurs, you can [roll back to legacy alerting]({{< relref "#rolling-back-to-legacy-alerting" >}}).
|
||||
Any errors encountered during the upgrade process will fail the upgrade and prevent Grafana from starting. If this occurs, you can [roll back to legacy alerting](#rolling-back-to-legacy-alerting).
|
||||
{{% /admonition %}}
|
||||
|
||||
1. **Upgrade to Grafana Alerting**:
|
||||
- **Enable Grafana Alerting**: [Modify custom configuration file]({{< relref "#enable-grafana-alerting" >}}).
|
||||
- **Enable Grafana Alerting**: [Modify custom configuration file](#enable-grafana-alerting).
|
||||
- **Restart Grafana**: Restart Grafana for the configuration changes to take effect. Grafana will automatically upgrade your existing alert rules and notification channels to the new Grafana Alerting system.
|
||||
1. **Review and Adjust Upgraded Alerts**:
|
||||
- **Review the upgraded alerts**: Go to the `Alerting` section of the navigation panel to review the upgraded alerts.
|
||||
@@ -136,7 +136,7 @@ enabled = true
|
||||
```
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
If you have existing legacy alerts we advise using the [Upgrade with Preview]({{< relref "#upgrade-with-preview-recommended" >}}) method first to ensure a smooth transition. Any organizations that have not completed the preview upgrade will automatically undergo the simple upgrade during the next restart.
|
||||
If you have existing legacy alerts we advise using the [Upgrade with Preview](#upgrade-with-preview-recommended) method first to ensure a smooth transition. Any organizations that have not completed the preview upgrade will automatically undergo the simple upgrade during the next restart.
|
||||
{{% /admonition %}}
|
||||
|
||||
### Rolling back to legacy alerting
|
||||
@@ -196,6 +196,9 @@ There are some differences between Grafana Alerting and legacy dashboard alerts,
|
||||
1. Since `Hipchat` and `Sensu` notification channels are no longer supported, legacy alerts associated with these channels are not automatically upgraded to Grafana Alerting. Assign the legacy alerts to a supported notification channel so that you continue to receive notifications for those alerts.
|
||||
|
||||
{{% docs/reference %}}
|
||||
|
||||
[feature-toggles]: "/docs/ -> /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/feature-toggles"
|
||||
|
||||
[alerting_config_error_handling]: "/docs/grafana/ -> /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/create-grafana-managed-rule#configure-no-data-and-error-handling"
|
||||
[alerting_config_error_handling]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/create-grafana-managed-rule#configure-no-data-and-error-handling"
|
||||
|
||||
|
||||
@@ -54,6 +54,5 @@ Refer to our [upgrade instructions][migrating-alerts].
|
||||
{{% docs/reference %}}
|
||||
[angular_deprecation]: "/docs/ -> /docs/grafana/<GRAFANA_VERSION>/developers/angular_deprecation"
|
||||
|
||||
[migrating-alerts]: "/docs/grafana/ -> /docs/grafana/<GRAFANA_VERSION>/alerting/set-up/migrating-alerts"
|
||||
[migrating-alerts]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/set-up/migrating-alerts"
|
||||
[migrating-alerts]: "/docs/ -> /docs/grafana/<GRAFANA_VERSION>/alerting/set-up/migrating-alerts"
|
||||
{{% /docs/reference %}}
|
||||
|
||||
+1
-2
@@ -32,7 +32,7 @@ For a complete guide about how Grafana provisions resources, refer to the [Provi
|
||||
|
||||
- You cannot edit provisioned resources from files in Grafana. You can only change the resource properties by changing the provisioning file and restarting Grafana or carrying out a hot reload. This prevents changes being made to the resource that would be overwritten if a file is provisioned again or a hot reload is carried out.
|
||||
|
||||
- Importing takes place during the initial set up of your Grafana system, but you can re-run it at any time using the [Grafana Admin API][reload-provisioning-configurations].
|
||||
- Importing takes place during the initial set up of your Grafana system, but you can re-run it at any time using the [Grafana Admin API](/docs/grafana/<GRAFANA_VERSION>/developers/http_api/admin#reload-provisioning-configurations).
|
||||
|
||||
- Importing an existing alerting resource results in a conflict. First, when present, remove the resources you plan to import.
|
||||
{{< /admonition >}}
|
||||
@@ -817,5 +817,4 @@ This eliminates the need for a persistent database to use Grafana Alerting in Ku
|
||||
|
||||
[provisioning]: "/docs/ -> /docs/grafana/<GRAFANA_VERSION>/administration/provisioning"
|
||||
|
||||
[reload-provisioning-configurations]: "/docs/ -> /docs/grafana/<GRAFANA_VERSION>/developers/http_api/admin#reload-provisioning-configurations"
|
||||
{{% /docs/reference %}}
|
||||
|
||||
@@ -27,6 +27,13 @@ In this tutorial, you'll learn how to use Grafana to set up a monitoring solutio
|
||||
- Annotate dashboards
|
||||
- Set up alerts
|
||||
|
||||
Alternatively, you can also watch our Grafana for Beginners series where we discuss fundamental concepts to help you get started with Grafana.
|
||||
|
||||
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
|
||||
<iframe src="https://www.youtube.com/embed/videoseries?si=ueLa_QEXz20IWnGt&list=PLDGkOdUX1Ujo27m6qiTPPCpFHVfyKq9jT" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
### Prerequisites
|
||||
@@ -128,7 +135,7 @@ To be able to visualize the metrics from Prometheus, you first need to add it as
|
||||
1. In the URL box, enter **http\://prometheus:9090**.
|
||||
1. Scroll to the bottom of the page and click **Save & test**.
|
||||
|
||||
Prometheus is now available as a data source in Grafana.
|
||||
You should see the message "Successfully queried the Prometheus API." This means Prometheus is now available as a data source in Grafana.
|
||||
|
||||
## Explore your metrics
|
||||
|
||||
@@ -136,7 +143,7 @@ Grafana Explore is a workflow for troubleshooting and data exploration. In this
|
||||
|
||||
> Ad-hoc queries are queries that are made interactively, with the purpose of exploring data. An ad-hoc query is commonly followed by another, more specific query.
|
||||
|
||||
1. Click the menu icon and, in the sidebar, click **Explore**. The Prometheus data source that you added will already be selected.
|
||||
1. Click the menu icon and, in the sidebar, click **Explore**. A dropdown menu for the list of available data sources is on the upper-left side. The Prometheus data source that you added will already be selected. If not, choose Prometheus.
|
||||
1. Confirm that you're in code mode by checking the **Builder/Code** toggle at the top right corner of the query panel.
|
||||
1. In the query editor, where it says _Enter a PromQL query…_, enter `tns_request_duration_seconds_count` and then press Shift + Enter.
|
||||
A graph appears.
|
||||
@@ -166,7 +173,7 @@ Grafana Explore is a workflow for troubleshooting and data exploration. In this
|
||||
|
||||
1. Back in Grafana, in the upper-right corner, click the _time picker_, and select **Last 5 minutes**. By zooming in on the last few minutes, it's easier to see when you receive new data.
|
||||
|
||||
Depending on your use case, you might want to group on other labels. Try grouping by other labels, such as `status_code`, by changing the `by(route)` part of the query.
|
||||
Depending on your use case, you might want to group on other labels. Try grouping by other labels, such as `status_code`, by changing the `by(route)` part of the query to `by(status_code)`.
|
||||
|
||||
## Add a logging data source
|
||||
|
||||
@@ -178,7 +185,7 @@ Grafana supports log data sources, like [Loki](/oss/loki/). Just like for metric
|
||||
1. In the URL box, enter [http://loki:3100](http://loki:3100).
|
||||
1. Scroll to the bottom of the page and click **Save & Test** to save your changes.
|
||||
|
||||
Loki is now available as a data source in Grafana.
|
||||
You should see the message "Data source successfully connected." Loki is now available as a data source in Grafana.
|
||||
|
||||
## Explore your logs
|
||||
|
||||
@@ -237,6 +244,10 @@ Every panel consists of a _query_ and a _visualization_. The query defines _what
|
||||
1. Click the **Save dashboard** (disk) icon at the top of the dashboard to save your dashboard.
|
||||
1. Enter a name in the **Dashboard name** field and then click **Save**.
|
||||
|
||||
You should now have a panel added to your dashboard.
|
||||
|
||||
{{< figure src="/media/tutorials/grafana-fundamentals-dashboard.png" alt="A panel in a Grafana dashboard" caption="A panel in a Grafana dashboard" >}}
|
||||
|
||||
## Annotate events
|
||||
|
||||
When things go bad, it often helps if you understand the context in which the failure occurred. Time of last deploy, system changes, or database migration can offer insight into what might have caused an outage. Annotations allow you to represent such events directly on your graphs.
|
||||
@@ -254,11 +265,13 @@ Grafana also lets you annotate a time interval, with _region annotations_.
|
||||
|
||||
Add a region annotation:
|
||||
|
||||
1. Press Ctrl (or Cmd on macOS), then click and drag across the graph to select an area.
|
||||
1. Press Ctrl (or Cmd on macOS) and hold, then click and drag across the graph to select an area.
|
||||
1. In **Description**, enter **Performed load tests**.
|
||||
1. In **Tags**, enter **testing**.
|
||||
1. Click **Save**.
|
||||
|
||||
### Using annotations to correlate logs with metrics
|
||||
|
||||
Manually annotating your dashboard is fine for those single events. For regularly occurring events, such as deploying a new release, Grafana supports querying annotations from one of your data sources. Let's create an annotation using the Loki data source we added earlier.
|
||||
|
||||
1. At the top of the dashboard, click the **Dashboard settings** (gear) icon.
|
||||
@@ -274,9 +287,13 @@ Manually annotating your dashboard is fine for those single events. For regularl
|
||||
1. Click **Apply**. Grafana displays the Annotations list, with your new annotation.
|
||||
1. Click on your dashboard name to return to your dashboard.
|
||||
1. At the top of your dashboard, there is now a toggle to display the results of the newly created annotation query. Press it if it's not already enabled.
|
||||
1. Click the **Save dashboard** icon to save the changes.
|
||||
1. To test the changes, go back to the [sample application](http://localhost:8081), post a new link without a URL to generate an error in your browser that says `empty url`.
|
||||
|
||||
The log lines returned by your query are now displayed as annotations in the graph.
|
||||
|
||||
{{< figure src="/media/tutorials/annotations-grafana-dashboard.png" alt="A panel in a Grafana dashboard with log queries from Loki displayed as annotations" caption="Displaying log queries from Loki as annotations" >}}
|
||||
|
||||
Being able to combine data from multiple data sources in one graph allows you to correlate information from both Prometheus and Loki.
|
||||
|
||||
Annotations also work very well alongside alerts. In the next and final section, we will set up an alert for our app `grafana.news` and then we will trigger it. This will provide a quick intro to our new Alerting platform.
|
||||
@@ -289,7 +306,16 @@ Grafana's new alerting platform debuted with Grafana 8. A year later, with Grafa
|
||||
|
||||
The most basic alert consists of two parts:
|
||||
|
||||
1. A _Contact point_ - A Contact point defines how Grafana delivers an alert. When the conditions of an _alert rule_ are met, Grafana notifies the contact points, or channels, configured for that alert. Some popular channels include email, webhooks, Slack notifications, and PagerDuty notifications.
|
||||
1. A _Contact point_ - A Contact point defines how Grafana delivers an alert. When the conditions of an _alert rule_ are met, Grafana notifies the contact points, or channels, configured for that alert.
|
||||
|
||||
Some popular channels include:
|
||||
|
||||
- Email
|
||||
- [Webhooks](#create-a-contact-point-for-grafana-managed-alerts)
|
||||
- [Telegram](https://grafana.com/blog/2023/12/28/how-to-integrate-grafana-alerting-and-telegram/)
|
||||
- Slack
|
||||
- PagerDuty
|
||||
|
||||
1. An _Alert rule_ - An Alert rule defines one or more _conditions_ that Grafana regularly evaluates. When these evaluations meet the rule's criteria, the alert is triggered.
|
||||
|
||||
To begin, let's set up a webhook contact point. Once we have a usable endpoint, we'll write an alert rule and trigger a notification.
|
||||
@@ -299,12 +325,10 @@ To begin, let's set up a webhook contact point. Once we have a usable endpoint,
|
||||
In this step, we'll set up a new contact point. This contact point will use the _webhooks_ channel. In order to make this work, we also need an endpoint for our webhook channel to receive the alert. We will use [requestbin.com](https://requestbin.com) to quickly set up that test endpoint. This way we can make sure that our alert is actually sending a notification somewhere.
|
||||
|
||||
1. Browse to [requestbin.com](https://requestbin.com).
|
||||
1. Under the **Create Request Bin** button
|
||||
1. From RequestBin, Copy the endpoint URL.
|
||||
1. Under the **Create Request Bin** button, click the link to create a **public bin** instead.
|
||||
1. From Request Bin, copy the endpoint URL.
|
||||
|
||||
Your request bin is now waiting for the first request.
|
||||
|
||||
<!-- 1. Copy the endpoint URL. -->
|
||||
Your Request Bin is now waiting for the first request.
|
||||
|
||||
Next, let's configure a Contact Point in Grafana's Alerting UI to send notifications to our Request Bin.
|
||||
|
||||
@@ -314,8 +338,8 @@ Next, let's configure a Contact Point in Grafana's Alerting UI to send notificat
|
||||
1. In **Integration**, choose **Webhook**.
|
||||
1. In **URL**, paste the endpoint to your request bin.
|
||||
|
||||
1. Click **Test** to send a test alert to your request bin.
|
||||
1. Navigate back to the request bin you created earlier. On the left side, there's now a `POST /` entry. Click it to see what information Grafana sent.
|
||||
1. Click **Test**, and then click **Send test notification** to send a test alert to your request bin.
|
||||
1. Navigate back to the Request Bin you created earlier. On the left side, there's now a `POST /` entry. Click it to see what information Grafana sent.
|
||||
1. Return to Grafana and click **Save contact point**.
|
||||
|
||||
We have now created a dummy webhook endpoint and created a new Alerting Contact Point in Grafana. Now we can create an alert rule and link it to this new channel.
|
||||
@@ -329,17 +353,24 @@ Now that Grafana knows how to notify us, it's time to set up an alert rule:
|
||||
1. For **Section 1**, name the rule `fundamentals-test`.
|
||||
1. For **Section 2**, Find the **query A** box. Choose your Prometheus datasource. Note that the rule type should automatically switch to Grafana-managed alert.
|
||||
1. Switch to code mode by checking the Builder/Code toggle.
|
||||
1. Enter the same query that we used in our earlier panel `sum(rate(tns_request_duration_seconds_count[5m])) by(route)`
|
||||
1. Enter the same Prometheus query that we used in our earlier panel:
|
||||
|
||||
```
|
||||
sum(rate(tns_request_duration_seconds_count[5m])) by(route)
|
||||
```
|
||||
|
||||
1. Press **Preview**. You should see some data returned.
|
||||
1. Keep expressions “B” and "C" as they are. These expressions (Reduce and Threshold, respectively) come by default when creating a new rule. Expression "B", selects the last value of our query “A”, while the Threshold expression "C" will check if the last value from expression "B" is above a specific value. In addition, the Threshold expression is the alert condition by default. Enter `0.2` as threshold value [You can read more about queries and conditions here](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/#expression-queries).
|
||||
1. In **Section 3**, in Folder, create a new folder, by typing a name for the folder. This folder will contain our alerts. For example: `fundamentals`. Then, click + add new or hit enter twice.
|
||||
1. Keep expressions “B” and "C" as they are. These expressions (Reduce and Threshold, respectively) come by default when creating a new rule. Expression "B", selects the last value of our query “A”, while the Threshold expression "C" will check if the last value from expression "B" is above a specific value. In addition, the Threshold expression is the alert condition by default. Enter `0.2` as threshold value. [You can read more about queries and conditions here](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/#expression-queries).
|
||||
1. In **Section 3**, in Folder, create a new folder, by clicking `New folder` and typing a name for the folder. This folder will contain our alerts. For example: `fundamentals`. Then, click `create`.
|
||||
1. In the Evaluation group, repeat the above step to create a new one. We will name it `fundamentals` too.
|
||||
1. Choose an Evaluation interval (how often the alert will be evaluated). For example, every `30s` (30 seconds).
|
||||
1. Set the pending period . This is the time that a condition has to be met until the alert enters in Firing state and a notification is sent. Enter `0s`. For the purposes of this tutorial, the evaluation interval is intentionally short. This makes it easier to test. This setting makes Grafana wait until an alert has fired for a given time before Grafana sends the notification.
|
||||
1. Choose an Evaluation interval (how often the alert will be evaluated). For example, every `10s` (10 seconds).
|
||||
1. Set the pending period. This is the time that a condition has to be met until the alert enters in Firing state and a notification is sent. Enter `0s`. For the purposes of this tutorial, the evaluation interval is intentionally short. This makes it easier to test. This setting makes Grafana wait until an alert has fired for a given time before Grafana sends the notification.
|
||||
1. In **Section 4**, you can optionally add some sample text to your summary message. [Read more about message templating here](/docs/grafana/latest/alerting/unified-alerting/message-templating/).
|
||||
1. Click **Save rule and exit** at the top of the page.
|
||||
1. In Grafana's sidebar, navigate to **Notification policies**.
|
||||
1. Under **Default policy**, select **...** › **Edit** and change the **Default contact point** to **RequestBin**.
|
||||
1. Under **Default policy**, select **...** › **Edit** and change the **Default contact point** from **grafana-default-email** to **RequestBin**.
|
||||
1. Expand the **Timing options** dropdown and under **Group wait** and **Group interval** update the value to `30s` for testing purposes. Group wait is the time Grafana waits before sending the first notification for a new group of alerts. In contrast, group interval is the time Grafana waits before sending notifications about changes to the group.
|
||||
1. Click **Update default policy**.
|
||||
|
||||
As a system grows, admins can use the **Notification policies** setting to organize and match alert rules to
|
||||
specific contact points.
|
||||
@@ -349,10 +380,26 @@ Now that Grafana knows how to notify us, it's time to set up an alert rule:
|
||||
We have now configured an alert rule and a contact point. Now let's see if we can trigger a Grafana Managed Alert by generating some traffic on our sample application.
|
||||
|
||||
1. Browse to [localhost:8081](http://localhost:8081).
|
||||
1. Repeatedly click the vote button or refresh the page to generate a traffic spike.
|
||||
1. Add a new title and URL, repeatedly click the vote button, or refresh the page to generate a traffic spike.
|
||||
|
||||
Once the query `sum(rate(tns_request_duration_seconds_count[5m])) by(route)` returns a value greater than `0.2` Grafana will trigger our alert. Browse to the Request Bin we created earlier and find the sent Grafana alert notification with details and metadata.
|
||||
|
||||
### Display Grafana Alerts to your dashboard
|
||||
|
||||
In most cases, it's also valuable to display Grafana Alerts as annotations to your dashboard. Let's see how we can configure this.
|
||||
|
||||
1. In Grafana's sidebar, hover over the **Alerting** (bell) icon and then click **Alert rules**.
|
||||
1. Expand the `fundamentals > fundamentals` folder to view our created alert rule.
|
||||
1. Click the **Edit** icon and scroll down to **Section 4**.
|
||||
1. Click the **Link dashboard and panel** button and select the dashboard and panel to which you want the alert to be added as an annotation.
|
||||
1. Click **Confirm** and **Save rule and exit** to save all the changes.
|
||||
1. In Grafana's sidebar, navigate to the dashboard by clicking **Dashboards** and selecting the dashboard you created.
|
||||
1. To test the changes, follow the steps listed to [trigger a Grafana Managed Alert](#trigger-a-grafana-managed-alert).
|
||||
|
||||
You should now see a red, broken heart icon beside the panel name, signifying that the alert has been triggered. An annotation for the alert, represented as a vertical red line, is also displayed.
|
||||
|
||||
{{< figure src="/media/tutorials/grafana-alert-on-dashboard.png" alt="A panel in a Grafana dashboard with alerting and annotations configured" caption="Displaying Grafana Alerts on a dashboard" >}}
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you learned about fundamental features of Grafana. To do so, we ran several Docker containers on your local machine. When you are ready to clean up this local tutorial environment, run the following command:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { e2e } from '../index';
|
||||
|
||||
export function waitForMonacoToLoad() {
|
||||
e2e.components.QueryField.container().children('[data-testid="Spinner"]').should('not.exist');
|
||||
cy.window().its('monaco').should('exist');
|
||||
cy.get('.monaco-editor textarea:first').should('exist');
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { e2e } from '../utils';
|
||||
import { waitForMonacoToLoad } from '../utils/support/monaco';
|
||||
|
||||
const dataSourceName = 'PromExemplar';
|
||||
const addDataSource = () => {
|
||||
@@ -57,12 +58,8 @@ describe('Exemplars', () => {
|
||||
// Switch to code editor
|
||||
e2e.components.RadioButton.container().filter(':contains("Code")').click();
|
||||
|
||||
// we need to wait for the query-field being lazy-loaded, in two steps:
|
||||
// 1. first we wait for the text 'Loading...' to appear
|
||||
// 1. then we wait for the text 'Loading...' to disappear
|
||||
const monacoLoadingText = 'Loading...';
|
||||
e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText);
|
||||
e2e.components.QueryField.container().should('be.visible').should('not.have.text', monacoLoadingText);
|
||||
// Wait for lazy loading Monaco
|
||||
waitForMonacoToLoad();
|
||||
|
||||
e2e.components.TimePicker.openButton().click();
|
||||
e2e.components.TimePicker.fromField().clear().type('2021-07-10 17:10:00');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { e2e } from '../utils';
|
||||
import { waitForMonacoToLoad } from '../utils/support/monaco';
|
||||
|
||||
const dataSourceName = 'LokiEditor';
|
||||
const addDataSource = () => {
|
||||
@@ -39,11 +40,7 @@ describe('Loki Query Editor', () => {
|
||||
|
||||
e2e.components.RadioButton.container().filter(':contains("Code")').click();
|
||||
|
||||
// Wait for lazy loading
|
||||
const monacoLoadingText = 'Loading...';
|
||||
|
||||
e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText);
|
||||
e2e.components.QueryField.container().should('be.visible').should('not.have.text', monacoLoadingText);
|
||||
waitForMonacoToLoad();
|
||||
|
||||
// adds closing braces around empty value
|
||||
e2e.components.QueryField.container().type('time(');
|
||||
|
||||
@@ -37,6 +37,9 @@ describe('MySQL datasource', () => {
|
||||
it.skip('code editor autocomplete should handle table name escaping/quoting', () => {
|
||||
e2e.components.RadioButton.container().filter(':contains("Code")').click();
|
||||
|
||||
e2e.components.CodeEditor.container().children('[data-testid="Spinner"]').should('not.exist');
|
||||
cy.window().its('monaco').should('exist');
|
||||
|
||||
cy.get('textarea').type('S{downArrow}{enter}');
|
||||
cy.wait('@tables');
|
||||
cy.get('.suggest-widget').contains(tableNameWithSpecialCharacter).should('be.visible');
|
||||
@@ -88,8 +91,10 @@ describe('MySQL datasource', () => {
|
||||
cy.get("[aria-label='Macros value selector']").should('be.visible').click();
|
||||
selectOption('timeFilter');
|
||||
|
||||
// Validate that the timeFilter macro was added
|
||||
e2e.components.CodeEditor.container().children('[data-testid="Spinner"]').should('not.exist');
|
||||
cy.window().its('monaco').should('exist');
|
||||
|
||||
// Validate that the timeFilter macro was added
|
||||
e2e.components.CodeEditor.container()
|
||||
.get('textarea')
|
||||
.should(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { e2e } from '../utils';
|
||||
import { waitForMonacoToLoad } from '../utils/support/monaco';
|
||||
|
||||
describe('Query editor', () => {
|
||||
beforeEach(() => {
|
||||
@@ -14,13 +15,8 @@ describe('Query editor', () => {
|
||||
|
||||
e2e.components.RadioButton.container().filter(':contains("Code")').click();
|
||||
|
||||
// we need to wait for the query-field being lazy-loaded, in two steps:
|
||||
// it is a two-step process:
|
||||
// 1. first we wait for the text 'Loading...' to appear
|
||||
// 1. then we wait for the text 'Loading...' to disappear
|
||||
const monacoLoadingText = 'Loading...';
|
||||
e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText);
|
||||
e2e.components.QueryField.container().should('be.visible').should('not.have.text', monacoLoadingText);
|
||||
waitForMonacoToLoad();
|
||||
|
||||
e2e.components.QueryField.container().type(queryText, { parseSpecialCharSequences: false }).type('{backspace}');
|
||||
|
||||
cy.contains(queryText.slice(0, -1)).should('be.visible');
|
||||
|
||||
+6
-1
@@ -15,6 +15,9 @@ const esModules = [
|
||||
'leven',
|
||||
'nanoid',
|
||||
'monaco-promql',
|
||||
'@kusto/monaco-kusto',
|
||||
'monaco-editor',
|
||||
'lodash-es',
|
||||
].join('|');
|
||||
|
||||
module.exports = {
|
||||
@@ -41,7 +44,9 @@ module.exports = {
|
||||
'\\.svg': '<rootDir>/public/test/mocks/svg.ts',
|
||||
'\\.css': '<rootDir>/public/test/mocks/style.ts',
|
||||
'react-inlinesvg': '<rootDir>/public/test/mocks/react-inlinesvg.tsx',
|
||||
'monaco-editor/esm/vs/editor/editor.api': '<rootDir>/public/test/mocks/monaco.ts',
|
||||
// resolve directly as monaco and kusto don't have main property in package.json which jest needs
|
||||
'^monaco-editor$': 'monaco-editor/esm/vs/editor/editor.api.js',
|
||||
'@kusto/monaco-kusto': '@kusto/monaco-kusto/release/esm/monaco.contribution.js',
|
||||
// near-membrane-dom won't work in a nodejs environment.
|
||||
'@locker/near-membrane-dom': '<rootDir>/public/test/mocks/nearMembraneDom.ts',
|
||||
'^@grafana/schema/dist/esm/(.*)$': '<rootDir>/packages/grafana-schema/src/$1',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Registry, RegistryItem } from '../utils/Registry';
|
||||
* @alpha
|
||||
*/
|
||||
export interface MonacoLanguageRegistryItem extends RegistryItem {
|
||||
init: () => Promise<void>;
|
||||
init: () => Worker;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,11 +62,3 @@ export function getPluginImportUtils(): PluginImportUtils {
|
||||
|
||||
return pluginImportUtils;
|
||||
}
|
||||
|
||||
// Grafana relies on RequireJS for Monaco Editor to load.
|
||||
// The SystemJS AMD extra creates a global define which causes RequireJS to silently bail.
|
||||
// Here we move and reset global define so Monaco Editor loader script continues to work.
|
||||
// @ts-ignore
|
||||
window.__grafana_amd_define = window.define;
|
||||
// @ts-ignore
|
||||
window.define = undefined;
|
||||
|
||||
@@ -119,14 +119,12 @@ class UnthemedCodeEditor extends PureComponent<Props> {
|
||||
}
|
||||
});
|
||||
|
||||
const languagePromise = this.loadCustomLanguage();
|
||||
|
||||
if (onChange) {
|
||||
editor.getModel()?.onDidChangeContent(() => onChange(editor.getValue()));
|
||||
}
|
||||
|
||||
if (onEditorDidMount) {
|
||||
languagePromise.then(() => onEditorDidMount(editor, monaco));
|
||||
onEditorDidMount(editor, monaco);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import MonacoEditor, { loader as monacoEditorLoader, Monaco } from '@monaco-editor/react';
|
||||
import Editor, { loader as monacoEditorLoader, Monaco } from '@monaco-editor/react';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { useTheme2 } from '../../themes';
|
||||
@@ -6,11 +7,8 @@ import { useTheme2 } from '../../themes';
|
||||
import defineThemes from './theme';
|
||||
import type { ReactMonacoEditorProps } from './types';
|
||||
|
||||
monacoEditorLoader.config({
|
||||
paths: {
|
||||
vs: (window.__grafana_public_path__ ?? 'public/') + 'lib/monaco/min/vs',
|
||||
},
|
||||
});
|
||||
// pass the monaco editor to the loader to bypass requirejs
|
||||
monacoEditorLoader.config({ monaco });
|
||||
|
||||
export const ReactMonacoEditor = (props: ReactMonacoEditorProps) => {
|
||||
const { beforeMount } = props;
|
||||
@@ -25,10 +23,6 @@ export const ReactMonacoEditor = (props: ReactMonacoEditorProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<MonacoEditor
|
||||
{...props}
|
||||
theme={theme.isDark ? 'grafana-dark' : 'grafana-light'}
|
||||
beforeMount={onMonacoBeforeMount}
|
||||
/>
|
||||
<Editor {...props} theme={theme.isDark ? 'grafana-dark' : 'grafana-light'} beforeMount={onMonacoBeforeMount} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestIntegrationPluginManager(t *testing.T) {
|
||||
tmpo := tempo.ProvideService(hcp)
|
||||
td := testdatasource.ProvideService()
|
||||
pg := postgres.ProvideService(cfg)
|
||||
my := mysql.ProvideService(cfg, hcp)
|
||||
my := mysql.ProvideService()
|
||||
ms := mssql.ProvideService(cfg)
|
||||
sv2 := searchV2.ProvideService(cfg, db.InitTestDB(t), nil, nil, tracer, features, nil, nil, nil)
|
||||
graf := grafanads.ProvideService(sv2, nil)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/sqleng"
|
||||
)
|
||||
|
||||
@@ -23,11 +22,11 @@ type mySQLMacroEngine struct {
|
||||
userError string
|
||||
}
|
||||
|
||||
func newMysqlMacroEngine(logger log.Logger, cfg *setting.Cfg) sqleng.SQLMacroEngine {
|
||||
func newMysqlMacroEngine(logger log.Logger, userFacingDefaultError string) sqleng.SQLMacroEngine {
|
||||
return &mySQLMacroEngine{
|
||||
SQLMacroEngineBase: sqleng.NewSQLMacroEngineBase(),
|
||||
logger: logger,
|
||||
userError: cfg.UserFacingDefaultError,
|
||||
userError: userFacingDefaultError,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -194,7 +193,7 @@ func TestMacroEngine(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMacroEngineConcurrency(t *testing.T) {
|
||||
engine := newMysqlMacroEngine(backend.NewLoggerWith("logger", "test"), setting.NewCfg())
|
||||
engine := newMysqlMacroEngine(backend.NewLoggerWith("logger", "test"), "error")
|
||||
query1 := backend.DataQuery{
|
||||
JSON: []byte{},
|
||||
}
|
||||
|
||||
+20
-13
@@ -22,8 +22,6 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/sqleng"
|
||||
)
|
||||
|
||||
@@ -34,7 +32,6 @@ const (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Cfg *setting.Cfg
|
||||
im instancemgmt.InstanceManager
|
||||
logger log.Logger
|
||||
}
|
||||
@@ -43,25 +40,30 @@ func characterEscape(s string, escapeChar string) string {
|
||||
return strings.ReplaceAll(s, escapeChar, url.QueryEscape(escapeChar))
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, httpClientProvider httpclient.Provider) *Service {
|
||||
func ProvideService() *Service {
|
||||
logger := backend.NewLoggerWith("logger", "tsdb.mysql")
|
||||
return &Service{
|
||||
im: datasource.NewInstanceManager(newInstanceSettings(cfg, logger)),
|
||||
im: datasource.NewInstanceManager(newInstanceSettings(logger)),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func newInstanceSettings(cfg *setting.Cfg, logger log.Logger) datasource.InstanceFactoryFunc {
|
||||
func newInstanceSettings(logger log.Logger) datasource.InstanceFactoryFunc {
|
||||
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||
cfg := backend.GrafanaConfigFromContext(ctx)
|
||||
sqlCfg, err := cfg.SQL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonData := sqleng.JsonData{
|
||||
MaxOpenConns: cfg.SqlDatasourceMaxOpenConnsDefault,
|
||||
MaxIdleConns: cfg.SqlDatasourceMaxIdleConnsDefault,
|
||||
ConnMaxLifetime: cfg.SqlDatasourceMaxConnLifetimeDefault,
|
||||
MaxOpenConns: sqlCfg.DefaultMaxOpenConns,
|
||||
MaxIdleConns: sqlCfg.DefaultMaxIdleConns,
|
||||
ConnMaxLifetime: sqlCfg.DefaultMaxConnLifetimeSeconds,
|
||||
SecureDSProxy: false,
|
||||
AllowCleartextPasswords: false,
|
||||
}
|
||||
|
||||
err := json.Unmarshal(settings.JSONData, &jsonData)
|
||||
err = json.Unmarshal(settings.JSONData, &jsonData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading settings: %w", err)
|
||||
}
|
||||
@@ -144,11 +146,16 @@ func newInstanceSettings(cfg *setting.Cfg, logger log.Logger) datasource.Instanc
|
||||
DSInfo: dsInfo,
|
||||
TimeColumnNames: []string{"time", "time_sec"},
|
||||
MetricColumnTypes: []string{"CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"},
|
||||
RowLimit: cfg.DataProxyRowLimit,
|
||||
RowLimit: sqlCfg.RowLimit,
|
||||
}
|
||||
|
||||
userFacingDefaultError, err := cfg.UserFacingDefaultError()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowTransformer := mysqlQueryResultTransformer{
|
||||
userError: cfg.UserFacingDefaultError,
|
||||
userError: userFacingDefaultError,
|
||||
}
|
||||
|
||||
db, err := sql.Open("mysql", cnnstr)
|
||||
@@ -160,7 +167,7 @@ func newInstanceSettings(cfg *setting.Cfg, logger log.Logger) datasource.Instanc
|
||||
db.SetMaxIdleConns(config.DSInfo.JsonData.MaxIdleConns)
|
||||
db.SetConnMaxLifetime(time.Duration(config.DSInfo.JsonData.ConnMaxLifetime) * time.Second)
|
||||
|
||||
return sqleng.NewQueryDataHandler(cfg.UserFacingDefaultError, db, config, &rowTransformer, newMysqlMacroEngine(logger, cfg), logger)
|
||||
return sqleng.NewQueryDataHandler(userFacingDefaultError, db, config, &rowTransformer, newMysqlMacroEngine(logger, userFacingDefaultError), logger)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/sqleng"
|
||||
)
|
||||
|
||||
@@ -67,7 +66,7 @@ func TestIntegrationMySQL(t *testing.T) {
|
||||
|
||||
db := InitMySQLTestDB(t, config.DSInfo.JsonData)
|
||||
|
||||
exe, err := sqleng.NewQueryDataHandler("", db, config, &rowTransformer, newMysqlMacroEngine(logger, setting.NewCfg()), logger)
|
||||
exe, err := sqleng.NewQueryDataHandler("", db, config, &rowTransformer, newMysqlMacroEngine(logger, ""), logger)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1179,7 +1178,7 @@ func TestIntegrationMySQL(t *testing.T) {
|
||||
|
||||
queryResultTransformer := mysqlQueryResultTransformer{}
|
||||
|
||||
handler, err := sqleng.NewQueryDataHandler("", db, config, &queryResultTransformer, newMysqlMacroEngine(logger, setting.NewCfg()), logger)
|
||||
handler, err := sqleng.NewQueryDataHandler("", db, config, &queryResultTransformer, newMysqlMacroEngine(logger, ""), logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("When doing a table query that returns 2 rows should limit the result to 1 row", func(t *testing.T) {
|
||||
|
||||
@@ -56,6 +56,7 @@ import { PluginPage } from './core/components/Page/PluginPage';
|
||||
import { GrafanaContextType, useReturnToPreviousInternal } from './core/context/GrafanaContext';
|
||||
import { initIconCache } from './core/icons/iconBundle';
|
||||
import { initializeI18n } from './core/internationalization';
|
||||
import { setMonacoEnv } from './core/monacoEnv';
|
||||
import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks';
|
||||
import { ModalManager } from './core/services/ModalManager';
|
||||
import { NewFrontendAssetsChecker } from './core/services/NewFrontendAssetsChecker';
|
||||
@@ -170,7 +171,9 @@ export class GrafanaApp {
|
||||
createAdHocVariableAdapter(),
|
||||
createSystemVariableAdapter(),
|
||||
]);
|
||||
|
||||
monacoLanguageRegistry.setInit(getDefaultMonacoLanguages);
|
||||
setMonacoEnv();
|
||||
|
||||
setQueryRunnerFactory(() => new QueryRunner());
|
||||
setVariableQueryRunner(new VariableQueryRunner());
|
||||
|
||||
@@ -48,12 +48,12 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick }: Props) {
|
||||
|
||||
// scroll active element into center if it's offscreen
|
||||
useEffect(() => {
|
||||
if (menuIsDocked && isActive && item.current && isElementOffscreen(item.current)) {
|
||||
if (isActive && item.current && isElementOffscreen(item.current)) {
|
||||
item.current.scrollIntoView({
|
||||
block: 'center',
|
||||
});
|
||||
}
|
||||
}, [isActive, menuIsDocked]);
|
||||
}, [isActive]);
|
||||
|
||||
if (!link.url) {
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataFrame, FALLBACK_COLOR, FieldType, TimeRange } from '@grafana/data';
|
||||
import { VisibilityMode, TimelineValueAlignment, TooltipDisplayMode } from '@grafana/schema';
|
||||
import { VisibilityMode, TimelineValueAlignment, TooltipDisplayMode, VizTooltipOptions } from '@grafana/schema';
|
||||
import { PanelContext, PanelContextRoot, UPlotConfigBuilder, VizLayout, VizLegend, VizLegendItem } from '@grafana/ui';
|
||||
|
||||
import { GraphNG, GraphNGProps } from '../GraphNG/GraphNG';
|
||||
@@ -18,6 +18,7 @@ export interface TimelineProps extends Omit<GraphNGProps, 'prepConfig' | 'propsT
|
||||
alignValue?: TimelineValueAlignment;
|
||||
colWidth?: number;
|
||||
legendItems?: VizLegendItem[];
|
||||
tooltip?: VizTooltipOptions;
|
||||
}
|
||||
|
||||
const propsToDiff = ['rowHeight', 'colWidth', 'showValue', 'mergeValues', 'alignValue', 'tooltip'];
|
||||
@@ -58,7 +59,7 @@ export class TimelineChart extends React.Component<TimelineProps> {
|
||||
// When there is only one row, use the full space
|
||||
rowHeight: alignedFrame.fields.length > 2 ? this.props.rowHeight : 1,
|
||||
getValueColor: this.getValueColor,
|
||||
// @ts-ignore
|
||||
|
||||
hoverMulti: this.props.tooltip?.mode === TooltipDisplayMode.Multi,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { monacoLanguageRegistry } from '@grafana/data';
|
||||
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
|
||||
|
||||
export function setMonacoEnv() {
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(_moduleId, label) {
|
||||
const language = monacoLanguageRegistry.getIfExists(label);
|
||||
|
||||
if (language) {
|
||||
return language.init();
|
||||
}
|
||||
|
||||
if (label === 'json') {
|
||||
return new Worker(new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url));
|
||||
}
|
||||
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url));
|
||||
}
|
||||
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return new Worker(new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url));
|
||||
}
|
||||
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new Worker(new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url));
|
||||
}
|
||||
|
||||
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ReducersMapObject } from '@reduxjs/toolkit';
|
||||
import { AnyAction, combineReducers } from 'redux';
|
||||
|
||||
import sharedReducers from 'app/core/reducers';
|
||||
import { migrateToCloudAPI } from 'app/features/admin/migrate-to-cloud/api';
|
||||
import ldapReducers from 'app/features/admin/state/reducers';
|
||||
import alertingReducers from 'app/features/alerting/state/reducers';
|
||||
import apiKeysReducers from 'app/features/api-keys/state/reducers';
|
||||
@@ -55,6 +56,7 @@ const rootReducers = {
|
||||
[alertingApi.reducerPath]: alertingApi.reducer,
|
||||
[publicDashboardApi.reducerPath]: publicDashboardApi.reducer,
|
||||
[browseDashboardsAPI.reducerPath]: browseDashboardsAPI.reducer,
|
||||
[migrateToCloudAPI.reducerPath]: migrateToCloudAPI.reducer,
|
||||
};
|
||||
|
||||
const addedReducers = {};
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
interface RequestOptions extends BackendSrvRequest {
|
||||
manageError?: (err: unknown) => { error: unknown };
|
||||
showErrorAlert?: boolean;
|
||||
}
|
||||
|
||||
function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn<RequestOptions> {
|
||||
async function backendSrvBaseQuery(requestOptions: RequestOptions) {
|
||||
try {
|
||||
const { data: responseData, ...meta } = await lastValueFrom(
|
||||
getBackendSrv().fetch({
|
||||
...requestOptions,
|
||||
url: baseURL + requestOptions.url,
|
||||
showErrorAlert: requestOptions.showErrorAlert,
|
||||
})
|
||||
);
|
||||
return { data: responseData, meta };
|
||||
} catch (error) {
|
||||
return requestOptions.manageError ? requestOptions.manageError(error) : { error };
|
||||
}
|
||||
}
|
||||
|
||||
return backendSrvBaseQuery;
|
||||
}
|
||||
|
||||
interface MigrateToCloudStatusDTO {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export const migrateToCloudAPI = createApi({
|
||||
reducerPath: 'migrateToCloudAPI',
|
||||
baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }),
|
||||
endpoints: (builder) => ({
|
||||
// TODO :)
|
||||
getStatus: builder.query<MigrateToCloudStatusDTO, void>({
|
||||
queryFn: () => ({ data: { enabled: false } }),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetStatusQuery } = migrateToCloudAPI;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
import { SetupServer, setupServer } from 'msw/node';
|
||||
|
||||
export function registerAPIHandlers(): SetupServer {
|
||||
const server = setupServer(
|
||||
// TODO
|
||||
http.get('/api/cloudmigration/status', () => {
|
||||
return HttpResponse.json({
|
||||
enabled: false,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
@@ -89,7 +89,7 @@ const ui = {
|
||||
|
||||
describe('Admin config', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
// FIXME: scope down
|
||||
grantUserPermissions(Object.values(AccessControlAction));
|
||||
mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export const SHARED_DEPENDENCY_PREFIX = 'package';
|
||||
export const LOAD_PLUGIN_CSS_REGEX = /^plugins.+\.css$/i;
|
||||
export const JS_CONTENT_TYPE_REGEX = /^(text|application)\/(x-)?javascript(;|$)/;
|
||||
export const AMD_MODULE_REGEX =
|
||||
/(?:^\uFEFF?|[^$_a-zA-Z\xA0-\uFFFF.])define\s*\(\s*("[^"]+"\s*,\s*|'[^']+'\s*,\s*)?\s*(\[(\s*(("[^"]+"|'[^']+')\s*,|\/\/.*\r?\n))*(\s*("[^"]+"|'[^']+')\s*,?)?(\s*(\/\/.*\r?\n|\/\*\/))*\s*\]|function\s*|{|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*\))/;
|
||||
|
||||
@@ -23,68 +23,6 @@ export const mockAmdModule = `define([], function() {
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleNamedNoDeps = `define("named", function() {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleNamedWithDeps = `define("named", ["dep"], function(dep) {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleNamedWithDeps2 = `define("named", ["dep", "dep2"], function(dep, dep2) {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleNamedWithDeps3 = `define("named", ["dep",
|
||||
"dep2"
|
||||
], function(dep, dep2) {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleOnlyFunction = `define(function() {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleWithComments = `/*! For license information please see module.js.LICENSE.txt */
|
||||
define(function(react) {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockAmdModuleWithComments2 = `/*! This is a commment */
|
||||
define(["dep"],
|
||||
/*! This is a commment */
|
||||
function(dep) {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
export const mockModuleWithDefineMethod = `ace.define(function() {
|
||||
return function() {
|
||||
console.log('AMD module loaded');
|
||||
var pluginPath = "/public/plugins/";
|
||||
}
|
||||
});`;
|
||||
|
||||
const server = setupServer(
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModule/module.js',
|
||||
@@ -112,78 +50,6 @@ const server = setupServer(
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleNamedNoDeps/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleNamedNoDeps, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleNamedWithDeps/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleNamedWithDeps, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleNamedWithDeps2/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleNamedWithDeps2, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleNamedWithDeps3/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleNamedWithDeps3, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleOnlyFunction/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleOnlyFunction, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleWithComments/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleWithComments, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockAmdModuleWithComments2/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockAmdModuleWithComments2, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
),
|
||||
http.get(
|
||||
'/public/plugins/mockModuleWithDefineMethod/module.js',
|
||||
() =>
|
||||
new HttpResponse(mockModuleWithDefineMethod, {
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript',
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as emotion from '@emotion/css';
|
||||
import * as emotionReact from '@emotion/react';
|
||||
import * as kusto from '@kusto/monaco-kusto';
|
||||
import * as d3 from 'd3';
|
||||
import * as i18next from 'i18next';
|
||||
import jquery from 'jquery';
|
||||
@@ -71,6 +72,7 @@ export const sharedDependenciesMap: Record<string, System.Module> = {
|
||||
'@grafana/runtime': grafanaRuntime,
|
||||
'@grafana/slate-react': slateReact, // for backwards compatibility with older plugins
|
||||
'@grafana/ui': grafanaUI,
|
||||
'@kusto/monaco-kusto': kusto,
|
||||
'app/core/app_events': {
|
||||
default: appEvents,
|
||||
__useDefault: true,
|
||||
|
||||
@@ -7,19 +7,7 @@ jest.mock('./cache', () => ({
|
||||
resolveWithCache: (url: string) => `${url}?_cache=1234`,
|
||||
}));
|
||||
|
||||
import {
|
||||
server,
|
||||
mockAmdModule,
|
||||
mockSystemModule,
|
||||
mockAmdModuleNamedNoDeps,
|
||||
mockAmdModuleNamedWithDeps,
|
||||
mockAmdModuleNamedWithDeps2,
|
||||
mockAmdModuleNamedWithDeps3,
|
||||
mockAmdModuleOnlyFunction,
|
||||
mockAmdModuleWithComments,
|
||||
mockModuleWithDefineMethod,
|
||||
mockAmdModuleWithComments2,
|
||||
} from './pluginLoader.mock';
|
||||
import { server } from './pluginLoader.mock';
|
||||
import { decorateSystemJSFetch, decorateSystemJSResolve } from './systemjsHooks';
|
||||
import { SystemJSWithLoaderHooks } from './types';
|
||||
|
||||
@@ -44,116 +32,6 @@ describe('SystemJS Loader Hooks', () => {
|
||||
});
|
||||
|
||||
describe('decorateSystemJSFetch', () => {
|
||||
it('wraps AMD modules in an AMD iife', async () => {
|
||||
const basicResult = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModule/module.js',
|
||||
{}
|
||||
);
|
||||
const basicSource = await basicResult.text();
|
||||
const basicExpected = `(function(define) {
|
||||
${mockAmdModule}
|
||||
})(window.__grafana_amd_define);`;
|
||||
expect(basicSource).toBe(basicExpected);
|
||||
|
||||
const mockAmdModuleNamedNoDepsResult = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleNamedNoDeps/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleNamedNoDepsSource = await mockAmdModuleNamedNoDepsResult.text();
|
||||
const mockAmdModuleNamedNoDepsExpected = `(function(define) {
|
||||
${mockAmdModuleNamedNoDeps}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleNamedNoDepsSource).toBe(mockAmdModuleNamedNoDepsExpected);
|
||||
|
||||
const mockAmdModuleNamedWithDepsResult = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleNamedWithDeps/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleNamedWithDepsSource = await mockAmdModuleNamedWithDepsResult.text();
|
||||
const mockAmdModuleNamedWithDepsExpected = `(function(define) {
|
||||
${mockAmdModuleNamedWithDeps}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleNamedWithDepsSource).toBe(mockAmdModuleNamedWithDepsExpected);
|
||||
|
||||
const mockAmdModuleNamedWithDeps2Result = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleNamedWithDeps2/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleNamedWithDeps2Source = await mockAmdModuleNamedWithDeps2Result.text();
|
||||
const mockAmdModuleNamedWithDeps2Expected = `(function(define) {
|
||||
${mockAmdModuleNamedWithDeps2}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleNamedWithDeps2Source).toBe(mockAmdModuleNamedWithDeps2Expected);
|
||||
|
||||
const mockAmdModuleNamedWithDeps3Result = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleNamedWithDeps3/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleNamedWithDeps3Source = await mockAmdModuleNamedWithDeps3Result.text();
|
||||
const mockAmdModuleNamedWithDeps3Expected = `(function(define) {
|
||||
${mockAmdModuleNamedWithDeps3}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleNamedWithDeps3Source).toBe(mockAmdModuleNamedWithDeps3Expected);
|
||||
|
||||
const mockAmdModuleOnlyFunctionResult = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleOnlyFunction/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleOnlyFunctionSource = await mockAmdModuleOnlyFunctionResult.text();
|
||||
const mockAmdModuleOnlyFunctionExpected = `(function(define) {
|
||||
${mockAmdModuleOnlyFunction}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleOnlyFunctionSource).toBe(mockAmdModuleOnlyFunctionExpected);
|
||||
|
||||
const mockAmdModuleWithCommentsResult = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleWithComments/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleWithCommentsSource = await mockAmdModuleWithCommentsResult.text();
|
||||
const mockAmdModuleWithCommentsExpected = `(function(define) {
|
||||
${mockAmdModuleWithComments}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleWithCommentsSource).toBe(mockAmdModuleWithCommentsExpected);
|
||||
|
||||
const mockAmdModuleWithComments2Result = await decorateSystemJSFetch(
|
||||
systemJSPrototype.fetch,
|
||||
'/public/plugins/mockAmdModuleWithComments2/module.js',
|
||||
{}
|
||||
);
|
||||
const mockAmdModuleWithComments2Source = await mockAmdModuleWithComments2Result.text();
|
||||
const mockAmdModuleWithComments2Expected = `(function(define) {
|
||||
${mockAmdModuleWithComments2}
|
||||
})(window.__grafana_amd_define);`;
|
||||
|
||||
expect(mockAmdModuleWithComments2Source).toBe(mockAmdModuleWithComments2Expected);
|
||||
});
|
||||
it("doesn't wrap system modules in an AMD iife", async () => {
|
||||
const url = '/public/plugins/mockSystemModule/module.js';
|
||||
const result = await decorateSystemJSFetch(systemJSPrototype.fetch, url, {});
|
||||
const source = await result.text();
|
||||
|
||||
expect(source).toBe(mockSystemModule);
|
||||
});
|
||||
it("doesn't wrap modules with a define method in an AMD iife", async () => {
|
||||
const url = '/public/plugins/mockModuleWithDefineMethod/module.js';
|
||||
const result = await decorateSystemJSFetch(systemJSPrototype.fetch, url, {});
|
||||
const source = await result.text();
|
||||
|
||||
expect(source).toBe(mockModuleWithDefineMethod);
|
||||
});
|
||||
it('only transforms plugin source code hosted on cdn with cdn paths', async () => {
|
||||
config.pluginsCDNBaseURL = 'http://my-cdn.com/plugins';
|
||||
const cdnUrl = 'http://my-cdn.com/plugins/my-plugin/v1.0.0/public/plugins/my-plugin/module.js';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { config, SystemJS } from '@grafana/runtime';
|
||||
import { transformPluginSourceForCDN } from '../cdn/utils';
|
||||
|
||||
import { resolveWithCache } from './cache';
|
||||
import { LOAD_PLUGIN_CSS_REGEX, JS_CONTENT_TYPE_REGEX, AMD_MODULE_REGEX, SHARED_DEPENDENCY_PREFIX } from './constants';
|
||||
import { LOAD_PLUGIN_CSS_REGEX, JS_CONTENT_TYPE_REGEX, SHARED_DEPENDENCY_PREFIX } from './constants';
|
||||
import { SystemJSWithLoaderHooks } from './types';
|
||||
import { isHostedOnCDN } from './utils';
|
||||
|
||||
@@ -19,10 +19,6 @@ export async function decorateSystemJSFetch(
|
||||
const source = await res.text();
|
||||
let transformedSrc = source;
|
||||
|
||||
if (AMD_MODULE_REGEX.test(transformedSrc)) {
|
||||
transformedSrc = preventAMDLoaderCollision(source);
|
||||
}
|
||||
|
||||
// JS files on the CDN need their asset paths transformed in the source
|
||||
if (isHostedOnCDN(res.url)) {
|
||||
const cdnTransformedSrc = transformPluginSourceForCDN({ url: res.url, source: transformedSrc });
|
||||
@@ -84,11 +80,3 @@ function getBackWardsCompatibleUrl(url: string) {
|
||||
|
||||
return hasValidFileExtension ? url : url + '.js';
|
||||
}
|
||||
|
||||
// This transform prevents a conflict between systemjs and requirejs which Monaco Editor
|
||||
// depends on. See packages/grafana-runtime/src/utils/plugin.ts for more.
|
||||
function preventAMDLoaderCollision(source: string) {
|
||||
return `(function(define) {
|
||||
${source}
|
||||
})(window.__grafana_amd_define);`;
|
||||
}
|
||||
|
||||
+2
-14
@@ -1,5 +1,4 @@
|
||||
import { EngineSchema, Schema } from '@kusto/monaco-kusto';
|
||||
import { Uri } from 'monaco-editor';
|
||||
import { EngineSchema, getKustoWorker } from '@kusto/monaco-kusto';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { CodeEditor, Monaco, MonacoEditor } from '@grafana/ui';
|
||||
@@ -13,16 +12,6 @@ interface MonacoEditorValues {
|
||||
monaco: Monaco;
|
||||
}
|
||||
|
||||
interface MonacoLanguages {
|
||||
kusto: {
|
||||
getKustoWorker: () => Promise<
|
||||
(url: Uri) => Promise<{
|
||||
setSchema: (schema: Schema) => void;
|
||||
}>
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
const QueryField = ({ query, onQueryChange, schema }: AzureQueryEditorFieldProps) => {
|
||||
const [monaco, setMonaco] = useState<MonacoEditorValues | undefined>();
|
||||
|
||||
@@ -33,10 +22,9 @@ const QueryField = ({ query, onQueryChange, schema }: AzureQueryEditorFieldProps
|
||||
|
||||
const setupEditor = async ({ monaco, editor }: MonacoEditorValues, schema: EngineSchema) => {
|
||||
try {
|
||||
const languages = monaco.languages as unknown as MonacoLanguages;
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
const kustoWorker = await languages.kusto.getKustoWorker();
|
||||
const kustoWorker = await getKustoWorker();
|
||||
const kustoMode = await kustoWorker(model?.uri);
|
||||
await kustoMode.setSchema(schema);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { CoreApp, DataSourcePluginMeta, PluginType } from '@grafana/data';
|
||||
import { BackendSrv, getBackendSrv, setBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { ParcaDataSource } from '../datasource';
|
||||
import { ProfileTypeMessage } from '../types';
|
||||
@@ -10,6 +12,17 @@ import { ProfileTypeMessage } from '../types';
|
||||
import { Props, QueryEditor } from './QueryEditor';
|
||||
|
||||
describe('QueryEditor', () => {
|
||||
let origBackendSrv: BackendSrv;
|
||||
const fetchMock = jest.fn().mockReturnValue(of({ data: [] }));
|
||||
|
||||
beforeEach(() => {
|
||||
origBackendSrv = getBackendSrv();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setBackendSrv(origBackendSrv);
|
||||
});
|
||||
|
||||
it('should render without error', async () => {
|
||||
setup();
|
||||
|
||||
@@ -17,6 +30,7 @@ describe('QueryEditor', () => {
|
||||
});
|
||||
|
||||
it('should render options', async () => {
|
||||
setBackendSrv({ ...origBackendSrv, fetch: fetchMock });
|
||||
setup();
|
||||
await openOptions();
|
||||
expect(screen.getByText(/Metric/)).toBeDefined();
|
||||
@@ -25,6 +39,7 @@ describe('QueryEditor', () => {
|
||||
});
|
||||
|
||||
it('should render correct options outside of explore', async () => {
|
||||
setBackendSrv({ ...origBackendSrv, fetch: fetchMock });
|
||||
setup({ props: { app: CoreApp.Dashboard } });
|
||||
await openOptions();
|
||||
expect(screen.getByText(/Metric/)).toBeDefined();
|
||||
|
||||
@@ -1,176 +1,7 @@
|
||||
import { forceSimulation, forceLink, forceCollide, forceX } from 'd3-force';
|
||||
import { layout } from './layout.worker.utils';
|
||||
|
||||
addEventListener('message', (event) => {
|
||||
const { nodes, edges, config } = event.data;
|
||||
layout(nodes, edges, config);
|
||||
postMessage({ nodes, edges });
|
||||
});
|
||||
|
||||
/**
|
||||
* Use d3 force layout to lay the nodes in a sensible way. This function modifies the nodes adding the x,y positions
|
||||
* and also fills in node references in edges instead of node ids.
|
||||
*/
|
||||
export function layout(nodes, edges, config) {
|
||||
// Start with some hardcoded positions so it starts laid out from left to right
|
||||
let { roots, secondLevelRoots } = initializePositions(nodes, edges);
|
||||
|
||||
// There always seems to be one or more root nodes each with single edge and we want to have them static on the
|
||||
// left neatly in something like grid layout
|
||||
[...roots, ...secondLevelRoots].forEach((n, index) => {
|
||||
n.fx = n.x;
|
||||
});
|
||||
|
||||
const simulation = forceSimulation(nodes)
|
||||
.force(
|
||||
'link',
|
||||
forceLink(edges)
|
||||
.id((d) => d.id)
|
||||
.distance(config.linkDistance)
|
||||
.strength(config.linkStrength)
|
||||
)
|
||||
// to keep the left to right layout we add force that pulls all nodes to right but because roots are fixed it will
|
||||
// apply only to non root nodes
|
||||
.force('x', forceX(config.forceX).strength(config.forceXStrength))
|
||||
// Make sure nodes don't overlap
|
||||
.force('collide', forceCollide(config.forceCollide));
|
||||
|
||||
// 300 ticks for the simulation are recommended but less would probably work too, most movement is done in first
|
||||
// few iterations and then all the forces gets smaller https://github.com/d3/d3-force#simulation_alphaDecay
|
||||
simulation.tick(config.tick);
|
||||
simulation.stop();
|
||||
|
||||
// We do centering here instead of using centering force to keep this more stable
|
||||
centerNodes(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* This initializes positions of the graph by going from the root to its children and laying it out in a grid from left
|
||||
* to right. This works only so, so because service map graphs can have cycles and children levels are not ordered in a
|
||||
* way to minimize the edge lengths. Nevertheless this seems to make the graph easier to nudge with the forces later on
|
||||
* than with the d3 default initial positioning. Also we can fix the root positions later on for a bit more neat
|
||||
* organisation.
|
||||
*
|
||||
* This function directly modifies the nodes given and only returns references to root nodes so they do not have to be
|
||||
* found again later on.
|
||||
*
|
||||
* How the spacing could look like approximately:
|
||||
* 0 - 0 - 0 - 0
|
||||
* \- 0 - 0 |
|
||||
* \- 0 -/
|
||||
* 0 - 0 -/
|
||||
*/
|
||||
function initializePositions(nodes, edges) {
|
||||
// To prevent going in cycles
|
||||
const alreadyPositioned = {};
|
||||
|
||||
const nodesMap = nodes.reduce((acc, node) => {
|
||||
acc[node.id] = node;
|
||||
return acc;
|
||||
}, {});
|
||||
const edgesMap = edges.reduce((acc, edge) => {
|
||||
const sourceId = edge.source;
|
||||
acc[sourceId] = [...(acc[sourceId] || []), edge];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
let roots = nodes.filter((n) => n.incoming === 0);
|
||||
|
||||
// For things like service maps we assume there is some root (client) node but if there is none then selecting
|
||||
// any node as a starting point should work the same.
|
||||
if (!roots.length) {
|
||||
roots = [nodes[0]];
|
||||
}
|
||||
|
||||
let secondLevelRoots = roots.reduce((acc, r) => {
|
||||
acc.push(...(edgesMap[r.id] ? edgesMap[r.id].map((e) => nodesMap[e.target]) : []));
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const rootYSpacing = 300;
|
||||
const nodeYSpacing = 200;
|
||||
const nodeXSpacing = 200;
|
||||
|
||||
let rootY = 0;
|
||||
for (const root of roots) {
|
||||
let graphLevel = [root];
|
||||
let x = 0;
|
||||
while (graphLevel.length > 0) {
|
||||
const nextGraphLevel = [];
|
||||
let y = rootY;
|
||||
for (const node of graphLevel) {
|
||||
if (alreadyPositioned[node.id]) {
|
||||
continue;
|
||||
}
|
||||
// Initialize positions based on the spacing in the grid
|
||||
node.x = x;
|
||||
node.y = y;
|
||||
alreadyPositioned[node.id] = true;
|
||||
|
||||
// Move to next Y position for next node
|
||||
y += nodeYSpacing;
|
||||
if (edgesMap[node.id]) {
|
||||
nextGraphLevel.push(...edgesMap[node.id].map((edge) => nodesMap[edge.target]));
|
||||
}
|
||||
}
|
||||
|
||||
graphLevel = nextGraphLevel;
|
||||
// Move to next X position for next level
|
||||
x += nodeXSpacing;
|
||||
// Reset Y back to baseline for this root
|
||||
y = rootY;
|
||||
}
|
||||
rootY += rootYSpacing;
|
||||
}
|
||||
return { roots, secondLevelRoots };
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the center of the graph based on its bound is in 0, 0 coordinates.
|
||||
* Modifies the nodes directly.
|
||||
*/
|
||||
function centerNodes(nodes) {
|
||||
const bounds = graphBounds(nodes);
|
||||
for (let node of nodes) {
|
||||
node.x = node.x - bounds.center.x;
|
||||
node.y = node.y - bounds.center.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounds of the graph meaning the extent of the nodes in all directions.
|
||||
*/
|
||||
function graphBounds(nodes) {
|
||||
if (nodes.length === 0) {
|
||||
return { top: 0, right: 0, bottom: 0, left: 0, center: { x: 0, y: 0 } };
|
||||
}
|
||||
|
||||
const bounds = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.x > acc.right) {
|
||||
acc.right = node.x;
|
||||
}
|
||||
if (node.x < acc.left) {
|
||||
acc.left = node.x;
|
||||
}
|
||||
if (node.y > acc.bottom) {
|
||||
acc.bottom = node.y;
|
||||
}
|
||||
if (node.y < acc.top) {
|
||||
acc.top = node.y;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity }
|
||||
);
|
||||
|
||||
const y = bounds.top + (bounds.bottom - bounds.top) / 2;
|
||||
const x = bounds.left + (bounds.right - bounds.left) / 2;
|
||||
|
||||
return {
|
||||
...bounds,
|
||||
center: {
|
||||
x,
|
||||
y,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// This file is a workaround so the layout function can be imported in Jest mocks. If the jest mock imports the
|
||||
// layout.worker.js file it will attach the eventlistener and then call the layout function with undefined data
|
||||
// which causes tests to fail.
|
||||
|
||||
import { forceSimulation, forceLink, forceCollide, forceX } from 'd3-force';
|
||||
|
||||
/**
|
||||
* Use d3 force layout to lay the nodes in a sensible way. This function modifies the nodes adding the x,y positions
|
||||
* and also fills in node references in edges instead of node ids.
|
||||
*/
|
||||
export function layout(nodes, edges, config) {
|
||||
// Start with some hardcoded positions so it starts laid out from left to right
|
||||
let { roots, secondLevelRoots } = initializePositions(nodes, edges);
|
||||
|
||||
// There always seems to be one or more root nodes each with single edge and we want to have them static on the
|
||||
// left neatly in something like grid layout
|
||||
[...roots, ...secondLevelRoots].forEach((n, index) => {
|
||||
n.fx = n.x;
|
||||
});
|
||||
|
||||
const simulation = forceSimulation(nodes)
|
||||
.force(
|
||||
'link',
|
||||
forceLink(edges)
|
||||
.id((d) => d.id)
|
||||
.distance(config.linkDistance)
|
||||
.strength(config.linkStrength)
|
||||
)
|
||||
// to keep the left to right layout we add force that pulls all nodes to right but because roots are fixed it will
|
||||
// apply only to non root nodes
|
||||
.force('x', forceX(config.forceX).strength(config.forceXStrength))
|
||||
// Make sure nodes don't overlap
|
||||
.force('collide', forceCollide(config.forceCollide));
|
||||
|
||||
// 300 ticks for the simulation are recommended but less would probably work too, most movement is done in first
|
||||
// few iterations and then all the forces gets smaller https://github.com/d3/d3-force#simulation_alphaDecay
|
||||
simulation.tick(config.tick);
|
||||
simulation.stop();
|
||||
|
||||
// We do centering here instead of using centering force to keep this more stable
|
||||
centerNodes(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* This initializes positions of the graph by going from the root to its children and laying it out in a grid from left
|
||||
* to right. This works only so, so because service map graphs can have cycles and children levels are not ordered in a
|
||||
* way to minimize the edge lengths. Nevertheless this seems to make the graph easier to nudge with the forces later on
|
||||
* than with the d3 default initial positioning. Also we can fix the root positions later on for a bit more neat
|
||||
* organisation.
|
||||
*
|
||||
* This function directly modifies the nodes given and only returns references to root nodes so they do not have to be
|
||||
* found again later on.
|
||||
*
|
||||
* How the spacing could look like approximately:
|
||||
* 0 - 0 - 0 - 0
|
||||
* \- 0 - 0 |
|
||||
* \- 0 -/
|
||||
* 0 - 0 -/
|
||||
*/
|
||||
function initializePositions(nodes, edges) {
|
||||
// To prevent going in cycles
|
||||
const alreadyPositioned = {};
|
||||
|
||||
const nodesMap = nodes.reduce((acc, node) => {
|
||||
acc[node.id] = node;
|
||||
return acc;
|
||||
}, {});
|
||||
const edgesMap = edges.reduce((acc, edge) => {
|
||||
const sourceId = edge.source;
|
||||
acc[sourceId] = [...(acc[sourceId] || []), edge];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
let roots = nodes.filter((n) => n.incoming === 0);
|
||||
|
||||
// For things like service maps we assume there is some root (client) node but if there is none then selecting
|
||||
// any node as a starting point should work the same.
|
||||
if (!roots.length) {
|
||||
roots = [nodes[0]];
|
||||
}
|
||||
|
||||
let secondLevelRoots = roots.reduce((acc, r) => {
|
||||
acc.push(...(edgesMap[r.id] ? edgesMap[r.id].map((e) => nodesMap[e.target]) : []));
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const rootYSpacing = 300;
|
||||
const nodeYSpacing = 200;
|
||||
const nodeXSpacing = 200;
|
||||
|
||||
let rootY = 0;
|
||||
for (const root of roots) {
|
||||
let graphLevel = [root];
|
||||
let x = 0;
|
||||
while (graphLevel.length > 0) {
|
||||
const nextGraphLevel = [];
|
||||
let y = rootY;
|
||||
for (const node of graphLevel) {
|
||||
if (alreadyPositioned[node.id]) {
|
||||
continue;
|
||||
}
|
||||
// Initialize positions based on the spacing in the grid
|
||||
node.x = x;
|
||||
node.y = y;
|
||||
alreadyPositioned[node.id] = true;
|
||||
|
||||
// Move to next Y position for next node
|
||||
y += nodeYSpacing;
|
||||
if (edgesMap[node.id]) {
|
||||
nextGraphLevel.push(...edgesMap[node.id].map((edge) => nodesMap[edge.target]));
|
||||
}
|
||||
}
|
||||
|
||||
graphLevel = nextGraphLevel;
|
||||
// Move to next X position for next level
|
||||
x += nodeXSpacing;
|
||||
// Reset Y back to baseline for this root
|
||||
y = rootY;
|
||||
}
|
||||
rootY += rootYSpacing;
|
||||
}
|
||||
return { roots, secondLevelRoots };
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the center of the graph based on its bound is in 0, 0 coordinates.
|
||||
* Modifies the nodes directly.
|
||||
*/
|
||||
function centerNodes(nodes) {
|
||||
const bounds = graphBounds(nodes);
|
||||
for (let node of nodes) {
|
||||
node.x = node.x - bounds.center.x;
|
||||
node.y = node.y - bounds.center.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounds of the graph meaning the extent of the nodes in all directions.
|
||||
*/
|
||||
function graphBounds(nodes) {
|
||||
if (nodes.length === 0) {
|
||||
return { top: 0, right: 0, bottom: 0, left: 0, center: { x: 0, y: 0 } };
|
||||
}
|
||||
|
||||
const bounds = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.x > acc.right) {
|
||||
acc.right = node.x;
|
||||
}
|
||||
if (node.x < acc.left) {
|
||||
acc.left = node.x;
|
||||
}
|
||||
if (node.y > acc.bottom) {
|
||||
acc.bottom = node.y;
|
||||
}
|
||||
if (node.y < acc.top) {
|
||||
acc.top = node.y;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity }
|
||||
);
|
||||
|
||||
const y = bounds.top + (bounds.bottom - bounds.top) / 2;
|
||||
const x = bounds.left + (bounds.right - bounds.left) / 2;
|
||||
|
||||
return {
|
||||
...bounds,
|
||||
center: {
|
||||
x,
|
||||
y,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
|
||||
|
||||
export default function loadKusto() {
|
||||
return new Promise<void>((resolve) =>
|
||||
__non_webpack_require__(['vs/language/kusto/monaco.contribution'], () => resolve())
|
||||
);
|
||||
return new Worker(new URL('@kusto/monaco-kusto/release/esm/kusto.worker', import.meta.url));
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const monaco = 'monaco';
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Config } from 'app/plugins/panel/nodeGraph/layout';
|
||||
import { EdgeDatum, NodeDatum } from 'app/plugins/panel/nodeGraph/types';
|
||||
|
||||
const { layout } = jest.requireActual('../../app/plugins/panel/nodeGraph/layout.worker.js');
|
||||
const { layout } = jest.requireActual('../../app/plugins/panel/nodeGraph/layout.worker.utils.js');
|
||||
|
||||
class LayoutMockWorker {
|
||||
timeout: number | undefined;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { configure } from '@testing-library/react';
|
||||
import i18next from 'i18next';
|
||||
import failOnConsole from 'jest-fail-on-console';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
@@ -20,3 +21,7 @@ i18next.use(initReactI18next).init({
|
||||
returnEmptyString: false,
|
||||
lng: 'en-US', // this should be the locale of the phrases in our source JSX
|
||||
});
|
||||
|
||||
// our tests are heavy in CI due to parallelisation and monaco and kusto
|
||||
// so we increase the default timeout to 2secs to avoid flakiness
|
||||
configure({ asyncUtilTimeout: 2000 });
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
@@ -52,7 +51,13 @@ module.exports = {
|
||||
string_decoder: false,
|
||||
},
|
||||
},
|
||||
ignoreWarnings: [/export .* was not found in/],
|
||||
ignoreWarnings: [
|
||||
/export .* was not found in/,
|
||||
{
|
||||
module: /@kusto\/language-service\/bridge\.min\.js$/,
|
||||
message: /^Critical dependency: the request of a dependency is an expression$/,
|
||||
},
|
||||
],
|
||||
stats: {
|
||||
children: false,
|
||||
source: false,
|
||||
@@ -65,25 +70,6 @@ module.exports = {
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
context: path.join(require.resolve('monaco-editor/package.json'), '../min/vs/'),
|
||||
from: '**/*',
|
||||
to: '../lib/monaco/min/vs/', // inside the public/build folder
|
||||
globOptions: {
|
||||
ignore: [
|
||||
'**/*.map', // debug files
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
context: path.join(require.resolve('@kusto/monaco-kusto/package.json'), '../release/min'),
|
||||
from: '**/*',
|
||||
to: '../lib/monaco/min/vs/language/kusto/',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
|
||||
@@ -399,6 +399,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/parser@npm:^7.22.15":
|
||||
version: 7.23.6
|
||||
resolution: "@babel/parser@npm:7.23.6"
|
||||
bin:
|
||||
parser: ./bin/babel-parser.js
|
||||
checksum: 10/6be3a63d3c9d07b035b5a79c022327cb7e16cbd530140ecb731f19a650c794c315a72c699a22413ebeafaff14aa8f53435111898d59e01a393d741b85629fa7d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.15, @babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.23.3":
|
||||
version: 7.23.3
|
||||
resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.23.3"
|
||||
@@ -1679,7 +1688,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/template@npm:^7.22.15, @babel/template@npm:^7.22.5, @babel/template@npm:^7.23.9, @babel/template@npm:^7.3.3":
|
||||
"@babel/template@npm:^7.22.15, @babel/template@npm:^7.3.3":
|
||||
version: 7.22.15
|
||||
resolution: "@babel/template@npm:7.22.15"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.22.13"
|
||||
"@babel/parser": "npm:^7.22.15"
|
||||
"@babel/types": "npm:^7.22.15"
|
||||
checksum: 10/21e768e4eed4d1da2ce5d30aa51db0f4d6d8700bc1821fec6292587df7bba2fe1a96451230de8c64b989740731888ebf1141138bfffb14cacccf4d05c66ad93f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/template@npm:^7.22.5, @babel/template@npm:^7.23.9":
|
||||
version: 7.23.9
|
||||
resolution: "@babel/template@npm:7.23.9"
|
||||
dependencies:
|
||||
@@ -1708,7 +1728,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
|
||||
version: 7.23.6
|
||||
resolution: "@babel/types@npm:7.23.6"
|
||||
dependencies:
|
||||
"@babel/helper-string-parser": "npm:^7.23.4"
|
||||
"@babel/helper-validator-identifier": "npm:^7.22.20"
|
||||
to-fast-properties: "npm:^2.0.0"
|
||||
checksum: 10/07e70bb94d30b0231396b5e9a7726e6d9227a0a62e0a6830c0bd3232f33b024092e3d5a7d1b096a65bbf2bb43a9ab4c721bf618e115bfbb87b454fa060f88cbf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.23.9":
|
||||
version: 7.23.9
|
||||
resolution: "@babel/types@npm:7.23.9"
|
||||
dependencies:
|
||||
@@ -3749,17 +3780,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/faro-core@npm:^1.3.6, @grafana/faro-core@npm:^1.3.7":
|
||||
version: 1.3.7
|
||||
resolution: "@grafana/faro-core@npm:1.3.7"
|
||||
dependencies:
|
||||
"@opentelemetry/api": "npm:^1.7.0"
|
||||
"@opentelemetry/otlp-transformer": "npm:^0.45.1"
|
||||
checksum: 10/fa3ff8dce1e6fe5ad91a4d42bb9bdb13f36d594074566a100645ffb4bc509265d18c78c5cda1ef8f39a3f043c1901baee620d3e044b3a0a6e9d1c516bf71f74f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/faro-core@npm:^1.3.8":
|
||||
"@grafana/faro-core@npm:^1.3.6, @grafana/faro-core@npm:^1.3.8":
|
||||
version: 1.3.8
|
||||
resolution: "@grafana/faro-core@npm:1.3.8"
|
||||
dependencies:
|
||||
@@ -3769,6 +3790,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/faro-core@npm:^1.3.7":
|
||||
version: 1.3.7
|
||||
resolution: "@grafana/faro-core@npm:1.3.7"
|
||||
dependencies:
|
||||
"@opentelemetry/api": "npm:^1.7.0"
|
||||
"@opentelemetry/otlp-transformer": "npm:^0.45.1"
|
||||
checksum: 10/fa3ff8dce1e6fe5ad91a4d42bb9bdb13f36d594074566a100645ffb4bc509265d18c78c5cda1ef8f39a3f043c1901baee620d3e044b3a0a6e9d1c516bf71f74f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/faro-web-sdk@npm:1.3.8":
|
||||
version: 1.3.8
|
||||
resolution: "@grafana/faro-web-sdk@npm:1.3.8"
|
||||
@@ -4737,6 +4768,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/resolve-uri@npm:3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "@jridgewell/resolve-uri@npm:3.1.0"
|
||||
checksum: 10/320ceb37af56953757b28e5b90c34556157676d41e3d0a3ff88769274d62373582bb0f0276a4f2d29c3f4fdd55b82b8be5731f52d391ad2ecae9b321ee1c742d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0":
|
||||
version: 3.1.1
|
||||
resolution: "@jridgewell/resolve-uri@npm:3.1.1"
|
||||
@@ -4751,6 +4789,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/source-map@npm:^0.3.2":
|
||||
version: 0.3.2
|
||||
resolution: "@jridgewell/source-map@npm:0.3.2"
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping": "npm:^0.3.0"
|
||||
"@jridgewell/trace-mapping": "npm:^0.3.9"
|
||||
checksum: 10/1aaa42075bac32a551708025da0c07b11c11fb05ccd10fb70df2cb0db88773338ab0f33f175d9865379cb855bb3b1cda478367747a1087309fda40a7b9214bfa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/source-map@npm:^0.3.3":
|
||||
version: 0.3.5
|
||||
resolution: "@jridgewell/source-map@npm:0.3.5"
|
||||
@@ -4761,6 +4809,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/sourcemap-codec@npm:1.4.14":
|
||||
version: 1.4.14
|
||||
resolution: "@jridgewell/sourcemap-codec@npm:1.4.14"
|
||||
checksum: 10/26e768fae6045481a983e48aa23d8fcd23af5da70ebd74b0649000e815e7fbb01ea2bc088c9176b3fffeb9bec02184e58f46125ef3320b30eaa1f4094cfefa38
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15":
|
||||
version: 1.4.15
|
||||
resolution: "@jridgewell/sourcemap-codec@npm:1.4.15"
|
||||
@@ -4778,7 +4833,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.21, @jridgewell/trace-mapping@npm:^0.3.9":
|
||||
"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9":
|
||||
version: 0.3.18
|
||||
resolution: "@jridgewell/trace-mapping@npm:0.3.18"
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri": "npm:3.1.0"
|
||||
"@jridgewell/sourcemap-codec": "npm:1.4.14"
|
||||
checksum: 10/f4fabdddf82398a797bcdbb51c574cd69b383db041a6cae1a6a91478681d6aab340c01af655cfd8c6e01cde97f63436a1445f08297cdd33587621cf05ffa0d55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/trace-mapping@npm:^0.3.20":
|
||||
version: 0.3.21
|
||||
resolution: "@jridgewell/trace-mapping@npm:0.3.21"
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri": "npm:^3.1.0"
|
||||
"@jridgewell/sourcemap-codec": "npm:^1.4.14"
|
||||
checksum: 10/925dda0620887e5a24f11b5a3a106f4e8b1a66155b49be6ceee61432174df33a17c243d8a89b2cd79ccebd281d817878759236a2fc42c47325ae9f73dfbfb90d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jridgewell/trace-mapping@npm:^0.3.21":
|
||||
version: 0.3.22
|
||||
resolution: "@jridgewell/trace-mapping@npm:0.3.22"
|
||||
dependencies:
|
||||
@@ -7096,13 +7171,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remix-run/router@npm:1.14.2, @remix-run/router@npm:^1.5.0":
|
||||
"@remix-run/router@npm:1.14.2":
|
||||
version: 1.14.2
|
||||
resolution: "@remix-run/router@npm:1.14.2"
|
||||
checksum: 10/422844e88b985f1e287301b302c6cf8169c9eea792f80d40464f97b25393bb2e697228ebd7a7b61444d5a51c5873c4a637aad20acde5886a5caf62e833c5ceee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remix-run/router@npm:^1.5.0":
|
||||
version: 1.11.0
|
||||
resolution: "@remix-run/router@npm:1.11.0"
|
||||
checksum: 10/629ec578b9dfd3c5cb5de64a0798dd7846ec5ba0351aa66f42b1c65efb43da8f30366be59b825303648965b0df55b638c110949b24ef94fd62e98117fdfb0c0f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/plugin-commonjs@npm:25.0.7":
|
||||
version: 25.0.7
|
||||
resolution: "@rollup/plugin-commonjs@npm:25.0.7"
|
||||
@@ -8410,6 +8492,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.3.90"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.4.0"
|
||||
@@ -8424,6 +8513,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-darwin-x64@npm:1.3.90"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-darwin-x64@npm:1.4.0"
|
||||
@@ -8438,6 +8534,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.90"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.0"
|
||||
@@ -8452,6 +8555,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.90"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.0"
|
||||
@@ -8466,6 +8576,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.3.90"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.4.0"
|
||||
@@ -8480,6 +8597,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.3.90"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.4.0"
|
||||
@@ -8494,6 +8618,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.3.90"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.4.0"
|
||||
@@ -8508,6 +8639,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.90"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.0"
|
||||
@@ -8522,6 +8660,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.90"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.0"
|
||||
@@ -8536,6 +8681,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.3.90":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.3.90"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.4.0"
|
||||
@@ -8550,7 +8702,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:1.4.0, @swc/core@npm:^1.3.49":
|
||||
"@swc/core@npm:1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@swc/core@npm:1.4.0"
|
||||
dependencies:
|
||||
@@ -8642,6 +8794,52 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:^1.3.49":
|
||||
version: 1.3.90
|
||||
resolution: "@swc/core@npm:1.3.90"
|
||||
dependencies:
|
||||
"@swc/core-darwin-arm64": "npm:1.3.90"
|
||||
"@swc/core-darwin-x64": "npm:1.3.90"
|
||||
"@swc/core-linux-arm-gnueabihf": "npm:1.3.90"
|
||||
"@swc/core-linux-arm64-gnu": "npm:1.3.90"
|
||||
"@swc/core-linux-arm64-musl": "npm:1.3.90"
|
||||
"@swc/core-linux-x64-gnu": "npm:1.3.90"
|
||||
"@swc/core-linux-x64-musl": "npm:1.3.90"
|
||||
"@swc/core-win32-arm64-msvc": "npm:1.3.90"
|
||||
"@swc/core-win32-ia32-msvc": "npm:1.3.90"
|
||||
"@swc/core-win32-x64-msvc": "npm:1.3.90"
|
||||
"@swc/counter": "npm:^0.1.1"
|
||||
"@swc/types": "npm:^0.1.5"
|
||||
peerDependencies:
|
||||
"@swc/helpers": ^0.5.0
|
||||
dependenciesMeta:
|
||||
"@swc/core-darwin-arm64":
|
||||
optional: true
|
||||
"@swc/core-darwin-x64":
|
||||
optional: true
|
||||
"@swc/core-linux-arm-gnueabihf":
|
||||
optional: true
|
||||
"@swc/core-linux-arm64-gnu":
|
||||
optional: true
|
||||
"@swc/core-linux-arm64-musl":
|
||||
optional: true
|
||||
"@swc/core-linux-x64-gnu":
|
||||
optional: true
|
||||
"@swc/core-linux-x64-musl":
|
||||
optional: true
|
||||
"@swc/core-win32-arm64-msvc":
|
||||
optional: true
|
||||
"@swc/core-win32-ia32-msvc":
|
||||
optional: true
|
||||
"@swc/core-win32-x64-msvc":
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 10/214af37af77b968203d495745a86db985734527f4696243bda5fb9ce868830d70e7a2cdbb268da2ee994d9fcedded25073d7b709fa09b75e96f9ba7d13a63da0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/counter@npm:^0.1.1, @swc/counter@npm:^0.1.2, @swc/counter@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "@swc/counter@npm:0.1.3"
|
||||
@@ -8649,7 +8847,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.5.6, @swc/helpers@npm:^0.5.0":
|
||||
"@swc/helpers@npm:0.5.6":
|
||||
version: 0.5.6
|
||||
resolution: "@swc/helpers@npm:0.5.6"
|
||||
dependencies:
|
||||
@@ -8658,6 +8856,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:^0.5.0":
|
||||
version: 0.5.1
|
||||
resolution: "@swc/helpers@npm:0.5.1"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/4954c4d2dd97bf965e863a10ffa44c3fdaf7653f2fa9ef1a6cf7ffffd67f3f832216588f9751afd75fdeaea60c4688c75c01e2405119c448f1a109c9a7958c54
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/types@npm:^0.1.5":
|
||||
version: 0.1.5
|
||||
resolution: "@swc/types@npm:0.1.5"
|
||||
@@ -8681,7 +8888,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/jest-dom@npm:6.4.2, @testing-library/jest-dom@npm:^6.1.2":
|
||||
"@testing-library/jest-dom@npm:6.4.2":
|
||||
version: 6.4.2
|
||||
resolution: "@testing-library/jest-dom@npm:6.4.2"
|
||||
dependencies:
|
||||
@@ -8714,6 +8921,39 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/jest-dom@npm:^6.1.2":
|
||||
version: 6.2.1
|
||||
resolution: "@testing-library/jest-dom@npm:6.2.1"
|
||||
dependencies:
|
||||
"@adobe/css-tools": "npm:^4.3.2"
|
||||
"@babel/runtime": "npm:^7.9.2"
|
||||
aria-query: "npm:^5.0.0"
|
||||
chalk: "npm:^3.0.0"
|
||||
css.escape: "npm:^1.5.1"
|
||||
dom-accessibility-api: "npm:^0.6.3"
|
||||
lodash: "npm:^4.17.15"
|
||||
redent: "npm:^3.0.0"
|
||||
peerDependencies:
|
||||
"@jest/globals": ">= 28"
|
||||
"@types/bun": "*"
|
||||
"@types/jest": ">= 28"
|
||||
jest: ">= 28"
|
||||
vitest: ">= 0.32"
|
||||
peerDependenciesMeta:
|
||||
"@jest/globals":
|
||||
optional: true
|
||||
"@types/bun":
|
||||
optional: true
|
||||
"@types/jest":
|
||||
optional: true
|
||||
jest:
|
||||
optional: true
|
||||
vitest:
|
||||
optional: true
|
||||
checksum: 10/e522314c4623d2030146570ed5907c71126477272760fa6bce7a2db4ee5dd1edf492ebc9a5f442e99f69d4d6e2a9d0aac49fae41323211dbcee186a1eca04707
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/react-hooks@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "@testing-library/react-hooks@npm:8.0.1"
|
||||
@@ -9800,7 +10040,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:20.11.19, @types/node@npm:^20.11.16":
|
||||
"@types/node@npm:20.11.19":
|
||||
version: 20.11.19
|
||||
resolution: "@types/node@npm:20.11.19"
|
||||
dependencies:
|
||||
@@ -9823,6 +10063,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^20.11.16":
|
||||
version: 20.11.18
|
||||
resolution: "@types/node@npm:20.11.18"
|
||||
dependencies:
|
||||
undici-types: "npm:~5.26.4"
|
||||
checksum: 10/eeaa55032e6702867e96d7b6f98df1d60af09d37ab72f2b905b349ec7e458dfb9c4d9cfc562962f5a51b156a968eea773d8025688f88b735944c81e3ac0e3b7f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/normalize-package-data@npm:^2.4.0":
|
||||
version: 2.4.1
|
||||
resolution: "@types/normalize-package-data@npm:2.4.1"
|
||||
@@ -9939,7 +10188,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:*, @types/react-dom@npm:18.2.19, @types/react-dom@npm:^18.0.0":
|
||||
"@types/react-dom@npm:*, @types/react-dom@npm:^18.0.0":
|
||||
version: 18.2.7
|
||||
resolution: "@types/react-dom@npm:18.2.7"
|
||||
dependencies:
|
||||
"@types/react": "npm:*"
|
||||
checksum: 10/9b70ef66cbe2d2898ea37eb79ee3697e0e4ad3d950e769a601f79be94097d43b8ef45b98a0b29528203c7d731c81666f637b2b7032deeced99214b4bc0662614
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:18.2.19":
|
||||
version: 18.2.19
|
||||
resolution: "@types/react-dom@npm:18.2.19"
|
||||
dependencies:
|
||||
@@ -11386,6 +11644,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn@npm:^8.5.0":
|
||||
version: 8.10.0
|
||||
resolution: "acorn@npm:8.10.0"
|
||||
bin:
|
||||
acorn: bin/acorn
|
||||
checksum: 10/522310c20fdc3c271caed3caf0f06c51d61cb42267279566edd1d58e83dbc12eebdafaab666a0f0be1b7ad04af9c6bc2a6f478690a9e6391c3c8b165ada917dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"add-dom-event-listener@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "add-dom-event-listener@npm:1.1.0"
|
||||
@@ -12080,13 +12347,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axe-core@npm:=4.7.0, axe-core@npm:^4.2.0":
|
||||
"axe-core@npm:=4.7.0":
|
||||
version: 4.7.0
|
||||
resolution: "axe-core@npm:4.7.0"
|
||||
checksum: 10/615c0f7722c3c9fcf353dbd70b00e2ceae234d4c17cbc839dd85c01d16797c4e4da45f8d27c6118e9e6b033fb06efd196106e13651a1b2f3a10e0f11c7b2f660
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axe-core@npm:^4.2.0":
|
||||
version: 4.6.3
|
||||
resolution: "axe-core@npm:4.6.3"
|
||||
checksum: 10/280f6a7067129875380f733ae84093ce29c4b8cfe36e1a8ff46bd5d2bcd57d093f11b00223ddf5fef98ca147e0e6568ddd0ada9415cf8ae15d379224bf3cbb51
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.0.0":
|
||||
version: 1.5.1
|
||||
resolution: "axios@npm:1.5.1"
|
||||
@@ -12651,7 +12925,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2":
|
||||
"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2":
|
||||
version: 4.22.2
|
||||
resolution: "browserslist@npm:4.22.2"
|
||||
dependencies:
|
||||
caniuse-lite: "npm:^1.0.30001565"
|
||||
electron-to-chromium: "npm:^1.4.601"
|
||||
node-releases: "npm:^2.0.14"
|
||||
update-browserslist-db: "npm:^1.0.13"
|
||||
bin:
|
||||
browserslist: cli.js
|
||||
checksum: 10/e3590793db7f66ad3a50817e7b7f195ce61e029bd7187200244db664bfbe0ac832f784e4f6b9c958aef8ea4abe001ae7880b7522682df521f4bc0a5b67660b5e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"browserslist@npm:^4.21.10":
|
||||
version: 4.22.3
|
||||
resolution: "browserslist@npm:4.22.3"
|
||||
dependencies:
|
||||
@@ -12968,6 +13256,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"caniuse-lite@npm:^1.0.30001565":
|
||||
version: 1.0.30001579
|
||||
resolution: "caniuse-lite@npm:1.0.30001579"
|
||||
checksum: 10/2cd0c02e5d66b09888743ad2b624dbde697ace5c76b55bfd6065ea033f6abea8ac3f5d3c9299c042f91b396e2141b49bc61f5e17086dc9ba3a866cc6790134c0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"canvas-hypertxt@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "canvas-hypertxt@npm:1.0.3"
|
||||
@@ -13239,14 +13534,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-spinners@npm:2.6.1, cli-spinners@npm:^2.5.0":
|
||||
"cli-spinners@npm:2.6.1":
|
||||
version: 2.6.1
|
||||
resolution: "cli-spinners@npm:2.6.1"
|
||||
checksum: 10/3e2dc5df72cf02120bebe256881fc8e3ec49867e5023d39f1e7340d7da57964f5236f4c75e568aa9dea6460b56f7a6d5870b89453c743c6c15e213cb52be2122
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-spinners@npm:^2.9.2":
|
||||
"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2":
|
||||
version: 2.9.2
|
||||
resolution: "cli-spinners@npm:2.9.2"
|
||||
checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794
|
||||
@@ -14168,7 +14463,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-loader@npm:6.10.0, css-loader@npm:^6.7.1":
|
||||
"css-loader@npm:6.10.0":
|
||||
version: 6.10.0
|
||||
resolution: "css-loader@npm:6.10.0"
|
||||
dependencies:
|
||||
@@ -14192,6 +14487,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-loader@npm:^6.7.1":
|
||||
version: 6.9.1
|
||||
resolution: "css-loader@npm:6.9.1"
|
||||
dependencies:
|
||||
icss-utils: "npm:^5.1.0"
|
||||
postcss: "npm:^8.4.33"
|
||||
postcss-modules-extract-imports: "npm:^3.0.0"
|
||||
postcss-modules-local-by-default: "npm:^4.0.4"
|
||||
postcss-modules-scope: "npm:^3.1.1"
|
||||
postcss-modules-values: "npm:^4.0.0"
|
||||
postcss-value-parser: "npm:^4.2.0"
|
||||
semver: "npm:^7.5.4"
|
||||
peerDependencies:
|
||||
webpack: ^5.0.0
|
||||
checksum: 10/6f897406188ed7f6db03daab0602ed86df1e967b48a048ab72d0ee223e59ab9e13c5235481b12deb79e12aadf0be43bc3bdee71e1dc1e875e4bcd91c05b464af
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-minimizer-webpack-plugin@npm:6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "css-minimizer-webpack-plugin@npm:6.0.0"
|
||||
@@ -15696,6 +16009,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.4.601":
|
||||
version: 1.4.625
|
||||
resolution: "electron-to-chromium@npm:1.4.625"
|
||||
checksum: 10/610a4eaabf6a064d8f6d4dfa25c55a3940f09a3b25edc8a271821d1b270bb28c4c9f19225d81bfc59deaa12c1f8f0144f3b4510631c6b6b47e0b6216737e216a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.4.648":
|
||||
version: 1.4.648
|
||||
resolution: "electron-to-chromium@npm:1.4.648"
|
||||
@@ -24128,14 +24448,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"outvariant@npm:^1.2.1, outvariant@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "outvariant@npm:1.4.0"
|
||||
checksum: 10/07b9bcb9b3a2ff1b3db02af6b07d70e663082b30ddc08ff475d7c85fc623fdcc4433a4ab5b88f6902b62dbb284eef1be386aa537e14cef0519fad887ec483054
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"outvariant@npm:^1.4.2":
|
||||
"outvariant@npm:^1.2.1, outvariant@npm:^1.4.0, outvariant@npm:^1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "outvariant@npm:1.4.2"
|
||||
checksum: 10/f16ba035fb65d1cbe7d2e06693dd42183c46bc8456713d9ddb5182d067defa7d78217edab0a2d3e173d3bacd627b2bd692195c7087c225b82548fbf52c677b38
|
||||
@@ -25167,7 +25480,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.13, postcss-selector-parser@npm:^6.0.15, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4":
|
||||
"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.13, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4":
|
||||
version: 6.0.13
|
||||
resolution: "postcss-selector-parser@npm:6.0.13"
|
||||
dependencies:
|
||||
cssesc: "npm:^3.0.0"
|
||||
util-deprecate: "npm:^1.0.2"
|
||||
checksum: 10/e779aa1f8ca9ee45d562400aac6109a2bccc59559b6e15adec8bc2a71d395ca563a378fd68f6a61963b4ef2ca190e0c0486e6dc6c41d755f3b82dd6e480e6941
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-selector-parser@npm:^6.0.15":
|
||||
version: 6.0.15
|
||||
resolution: "postcss-selector-parser@npm:6.0.15"
|
||||
dependencies:
|
||||
@@ -26841,7 +27164,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-use@npm:17.5.0, react-use@npm:^17.4.2":
|
||||
"react-use@npm:17.5.0":
|
||||
version: 17.5.0
|
||||
resolution: "react-use@npm:17.5.0"
|
||||
dependencies:
|
||||
@@ -26866,6 +27189,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-use@npm:^17.4.2":
|
||||
version: 17.4.2
|
||||
resolution: "react-use@npm:17.4.2"
|
||||
dependencies:
|
||||
"@types/js-cookie": "npm:^2.2.6"
|
||||
"@xobotyi/scrollbar-width": "npm:^1.9.5"
|
||||
copy-to-clipboard: "npm:^3.3.1"
|
||||
fast-deep-equal: "npm:^3.1.3"
|
||||
fast-shallow-equal: "npm:^1.0.0"
|
||||
js-cookie: "npm:^2.2.1"
|
||||
nano-css: "npm:^5.6.1"
|
||||
react-universal-interface: "npm:^0.6.2"
|
||||
resize-observer-polyfill: "npm:^1.5.1"
|
||||
screenfull: "npm:^5.1.0"
|
||||
set-harmonic-interval: "npm:^1.0.1"
|
||||
throttle-debounce: "npm:^3.0.1"
|
||||
ts-easing: "npm:^0.2.0"
|
||||
tslib: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
react: "*"
|
||||
react-dom: "*"
|
||||
checksum: 10/56d2da474d949d22eb34ff3ffccf5526986d51ed68a8f4e64f4b79bdcff3f0ea55d322c104e3fc0819b08b8765e8eb3fa47d8b506e9d61ff1fdc7bd1374c17d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-virtual@npm:2.10.4, react-virtual@npm:^2.8.2":
|
||||
version: 2.10.4
|
||||
resolution: "react-virtual@npm:2.10.4"
|
||||
@@ -28260,14 +28608,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"signal-exit@npm:^4.0.1":
|
||||
version: 4.0.2
|
||||
resolution: "signal-exit@npm:4.0.2"
|
||||
checksum: 10/99d49eab7f24aeed79e44999500d5ff4b9fbb560b0e1f8d47096c54d625b995aeaec3032cce44527adf2de0c303731a8356e234a348d6801214a8a3385a1ff8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"signal-exit@npm:^4.1.0":
|
||||
"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "signal-exit@npm:4.1.0"
|
||||
checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f
|
||||
@@ -29695,7 +30036,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser-webpack-plugin@npm:5.3.10, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.7":
|
||||
"terser-webpack-plugin@npm:5.3.10, terser-webpack-plugin@npm:^5.3.10":
|
||||
version: 5.3.10
|
||||
resolution: "terser-webpack-plugin@npm:5.3.10"
|
||||
dependencies:
|
||||
@@ -29717,6 +30058,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.7":
|
||||
version: 5.3.9
|
||||
resolution: "terser-webpack-plugin@npm:5.3.9"
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping": "npm:^0.3.17"
|
||||
jest-worker: "npm:^27.4.5"
|
||||
schema-utils: "npm:^3.1.1"
|
||||
serialize-javascript: "npm:^6.0.1"
|
||||
terser: "npm:^5.16.8"
|
||||
peerDependencies:
|
||||
webpack: ^5.1.0
|
||||
peerDependenciesMeta:
|
||||
"@swc/core":
|
||||
optional: true
|
||||
esbuild:
|
||||
optional: true
|
||||
uglify-js:
|
||||
optional: true
|
||||
checksum: 10/339737a407e034b7a9d4a66e31d84d81c10433e41b8eae2ca776f0e47c2048879be482a9aa08e8c27565a2a949bc68f6e07f451bf4d9aa347dd61b3d000f5353
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser@npm:^5.0.0, terser@npm:^5.15.1, terser@npm:^5.26.0, terser@npm:^5.7.2":
|
||||
version: 5.27.0
|
||||
resolution: "terser@npm:5.27.0"
|
||||
@@ -29731,6 +30094,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser@npm:^5.16.8":
|
||||
version: 5.17.2
|
||||
resolution: "terser@npm:5.17.2"
|
||||
dependencies:
|
||||
"@jridgewell/source-map": "npm:^0.3.2"
|
||||
acorn: "npm:^8.5.0"
|
||||
commander: "npm:^2.20.0"
|
||||
source-map-support: "npm:~0.5.20"
|
||||
bin:
|
||||
terser: bin/terser
|
||||
checksum: 10/6df529586a4913657547dd8bfe2b5a59704b7acbe4e49ac938a16f829a62226f98dafb19c88b7af66b245ea281ee5dbeec33a41349ac3c035855417b06ebd646
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"test-exclude@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "test-exclude@npm:6.0.0"
|
||||
|
||||
Reference in New Issue
Block a user