From 9c42826c30eafcc929d3c16e5efd995e5759b0ec Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:31:59 +0100 Subject: [PATCH 1/7] Alerting docs: fix broken link and apply the Writer toolkit guideline to other links (#83209) * Fix `/docs/reference` cannot be used within admonitions * Remove `relref` links to apply Writers toolkit guidelines --- .../create-mimir-loki-managed-recording-rule.md | 3 +-- .../images-in-notifications.md | 4 ++-- .../using-go-templating-language.md | 2 +- .../alerting/set-up/migrating-alerts/_index.md | 17 ++++++++++------- .../legacy-alerting-deprecation.md | 3 +-- .../file-provisioning/index.md | 3 +-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-recording-rule.md b/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-recording-rule.md index bedc33a981b..08fbbbc4929 100644 --- a/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-recording-rule.md +++ b/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-recording-rule.md @@ -68,6 +68,5 @@ To create recording rules, follow these steps. [annotation-label]: "/docs/grafana/ -> /docs/grafana//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//setup-grafana/configure-grafana" -[configure-grafana]: "/docs/grafana-cloud/ -> /docs/grafana//setup-grafana/configure-grafana" +[configure-grafana]: "/docs/ -> /docs/grafana//setup-grafana/configure-grafana" {{% /docs/reference %}} diff --git a/docs/sources/alerting/manage-notifications/images-in-notifications.md b/docs/sources/alerting/manage-notifications/images-in-notifications.md index 548b76e4482..89807f1c8be 100644 --- a/docs/sources/alerting/manage-notifications/images-in-notifications.md +++ b/docs/sources/alerting/manage-notifications/images-in-notifications.md @@ -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. diff --git a/docs/sources/alerting/manage-notifications/template-notifications/using-go-templating-language.md b/docs/sources/alerting/manage-notifications/template-notifications/using-go-templating-language.md index 2580962b850..50115d3f23e 100644 --- a/docs/sources/alerting/manage-notifications/template-notifications/using-go-templating-language.md +++ b/docs/sources/alerting/manage-notifications/template-notifications/using-go-templating-language.md @@ -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 }} diff --git a/docs/sources/alerting/set-up/migrating-alerts/_index.md b/docs/sources/alerting/set-up/migrating-alerts/_index.md index abb489c723f..42da3a7fcd6 100644 --- a/docs/sources/alerting/set-up/migrating-alerts/_index.md +++ b/docs/sources/alerting/set-up/migrating-alerts/_index.md @@ -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//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//setup-grafana/configure-grafana/feature-toggles" + [alerting_config_error_handling]: "/docs/grafana/ -> /docs/grafana//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" diff --git a/docs/sources/alerting/set-up/migrating-alerts/legacy-alerting-deprecation.md b/docs/sources/alerting/set-up/migrating-alerts/legacy-alerting-deprecation.md index d9955483b3c..2bdef82ee95 100644 --- a/docs/sources/alerting/set-up/migrating-alerts/legacy-alerting-deprecation.md +++ b/docs/sources/alerting/set-up/migrating-alerts/legacy-alerting-deprecation.md @@ -54,6 +54,5 @@ Refer to our [upgrade instructions][migrating-alerts]. {{% docs/reference %}} [angular_deprecation]: "/docs/ -> /docs/grafana//developers/angular_deprecation" -[migrating-alerts]: "/docs/grafana/ -> /docs/grafana//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//alerting/set-up/migrating-alerts" {{% /docs/reference %}} diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md index e0ce89688f5..c07d9220472 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md @@ -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//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//administration/provisioning" -[reload-provisioning-configurations]: "/docs/ -> /docs/grafana//developers/http_api/admin#reload-provisioning-configurations" {{% /docs/reference %}} From 5561ace46750dd25467736375481ca143b693288 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 22 Feb 2024 11:04:18 +0000 Subject: [PATCH 2/7] Navigation: Scroll the active menu item into view when menu is an overlay (#83212) scroll the active menu item into view even when not docked --- .../app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx index d91514f8787..528c0c9a04f 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx @@ -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; From 0dbf2da2549d20848266e91dd367d15cb9f17eec Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 22 Feb 2024 11:12:00 +0000 Subject: [PATCH 3/7] MigrateToCloud: Add API interface for frontend (#83215) Add RTK Query mocks --- public/app/core/reducers/root.ts | 2 + .../admin/migrate-to-cloud/MigrateToCloud.tsx | 14 +++++- .../features/admin/migrate-to-cloud/api.ts | 45 +++++++++++++++++++ .../admin/migrate-to-cloud/fixtures/mswAPI.ts | 15 +++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 public/app/features/admin/migrate-to-cloud/api.ts create mode 100644 public/app/features/admin/migrate-to-cloud/fixtures/mswAPI.ts diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index f2c436062bd..33d86889aee 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -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 = {}; diff --git a/public/app/features/admin/migrate-to-cloud/MigrateToCloud.tsx b/public/app/features/admin/migrate-to-cloud/MigrateToCloud.tsx index a213fe2ae60..f3d09ecc0ae 100644 --- a/public/app/features/admin/migrate-to-cloud/MigrateToCloud.tsx +++ b/public/app/features/admin/migrate-to-cloud/MigrateToCloud.tsx @@ -1,7 +1,19 @@ import React from 'react'; +import { Stack, Text } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; +import { useGetStatusQuery } from './api'; + export default function MigrateToCloud() { - return TODO; + const { data } = useGetStatusQuery(); + + return ( + + + TODO +
{JSON.stringify(data)}
+
+
+ ); } diff --git a/public/app/features/admin/migrate-to-cloud/api.ts b/public/app/features/admin/migrate-to-cloud/api.ts new file mode 100644 index 00000000000..d1972ce85c9 --- /dev/null +++ b/public/app/features/admin/migrate-to-cloud/api.ts @@ -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 { + 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({ + queryFn: () => ({ data: { enabled: false } }), + }), + }), +}); + +export const { useGetStatusQuery } = migrateToCloudAPI; diff --git a/public/app/features/admin/migrate-to-cloud/fixtures/mswAPI.ts b/public/app/features/admin/migrate-to-cloud/fixtures/mswAPI.ts new file mode 100644 index 00000000000..d8b9748e525 --- /dev/null +++ b/public/app/features/admin/migrate-to-cloud/fixtures/mswAPI.ts @@ -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; +} From 0dcdfc261b781df8c604ccac904dc500a63d327b Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Thu, 22 Feb 2024 12:31:40 +0100 Subject: [PATCH 4/7] Monaco Editor: Load via ESM (#78261) * chore(monaco): bump monaco-editor to latest version * feat(codeeditor): use esm to load monaco editor * revert(monaco): put back previous version * feat(monaco): setup MonacoEnvironment when bootstrapping app * feat(monaco): load monaco languages from registry as workers * feat(webpack): clean up warnings, remove need to copy monaco into lib * fix(plugins): wip - remove amd loader workaround in systemjs hooks * chore(azure): clean up so QueryField passes typecheck * test(jest): update config to fix failing tests due to missing monaco-editor * test(jest): update config to work with monaco-editor and kusto * test(jest): prevent message eventlistener in nodeGraph/layout.worker tripping up monaco tests * test(plugins): wip - remove amd related tests from systemjs hooks * test(alerting): prefer clearAllMocks to prevent monaco editor failing due to missing matchMedia * test(parca): fix failing test due to undefined backendSrv * chore: move monacoEnv to app/core * test: increase testing-lib timeout to 2secs, fix parca test to assert dom element * feat(plugins): share kusto via systemjs * test(e2e): increase timeout for checking monaco editor in exemplars spec * test(e2e): assert monaco has loaded by checking the spinner is gone and window.monaco exists * test(e2e): check for monaco editor textarea * test(e2e): check monaco editor is loaded before assertions * test(e2e): add waitForMonacoToLoad util to reduce duplication * test(e2e): fix failing mysql spec * chore(jest): add comment to setupTests explaining need to incresae default timeout * chore(nodegraph): improve comment in layout.worker.utils to better explain the need for file --- .betterer.results | 4 - e2e/utils/support/monaco.ts | 7 + e2e/various-suite/exemplars.spec.ts | 9 +- e2e/various-suite/loki-editor.spec.ts | 7 +- e2e/various-suite/mysql.spec.ts | 7 +- e2e/various-suite/query-editor.spec.ts | 10 +- jest.config.js | 7 +- .../src/monaco/languageRegistry.ts | 2 +- packages/grafana-runtime/src/utils/plugin.ts | 8 - .../src/components/Monaco/CodeEditor.tsx | 4 +- .../components/Monaco/ReactMonacoEditor.tsx | 16 +- public/app/app.ts | 3 + public/app/core/monacoEnv.ts | 32 ++ .../admin/AlertmanagerConfig.test.tsx | 2 +- .../app/features/plugins/loader/constants.ts | 2 - .../plugins/loader/pluginLoader.mock.ts | 134 ----- .../plugins/loader/sharedDependencies.ts | 2 + .../plugins/loader/systemjsHooks.test.ts | 124 +---- .../features/plugins/loader/systemjsHooks.ts | 14 +- .../components/LogsQueryEditor/QueryField.tsx | 16 +- .../parca/QueryEditor/QueryEditor.test.tsx | 15 + .../plugins/panel/nodeGraph/layout.worker.js | 171 +------ .../panel/nodeGraph/layout.worker.utils.js | 174 +++++++ public/lib/monaco-languages/kusto.ts | 6 +- public/test/mocks/monaco.ts | 1 - public/test/mocks/workers.ts | 2 +- public/test/setupTests.ts | 5 + scripts/webpack/webpack.common.js | 28 +- yarn.lock | 465 ++++++++++++++++-- 29 files changed, 703 insertions(+), 574 deletions(-) create mode 100644 e2e/utils/support/monaco.ts create mode 100644 public/app/core/monacoEnv.ts create mode 100644 public/app/plugins/panel/nodeGraph/layout.worker.utils.js delete mode 100644 public/test/mocks/monaco.ts diff --git a/.betterer.results b/.betterer.results index 5c20e6abc90..f04979e6beb 100644 --- a/.betterer.results +++ b/.betterer.results @@ -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"] ], diff --git a/e2e/utils/support/monaco.ts b/e2e/utils/support/monaco.ts new file mode 100644 index 00000000000..54c528877ed --- /dev/null +++ b/e2e/utils/support/monaco.ts @@ -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'); +} diff --git a/e2e/various-suite/exemplars.spec.ts b/e2e/various-suite/exemplars.spec.ts index b83273832a7..0a7981b03ac 100644 --- a/e2e/various-suite/exemplars.spec.ts +++ b/e2e/various-suite/exemplars.spec.ts @@ -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'); diff --git a/e2e/various-suite/loki-editor.spec.ts b/e2e/various-suite/loki-editor.spec.ts index 189c9d5a131..42bf063c137 100644 --- a/e2e/various-suite/loki-editor.spec.ts +++ b/e2e/various-suite/loki-editor.spec.ts @@ -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('); diff --git a/e2e/various-suite/mysql.spec.ts b/e2e/various-suite/mysql.spec.ts index e35f5c6fd1c..b78db4bfe18 100644 --- a/e2e/various-suite/mysql.spec.ts +++ b/e2e/various-suite/mysql.spec.ts @@ -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( diff --git a/e2e/various-suite/query-editor.spec.ts b/e2e/various-suite/query-editor.spec.ts index 2263f1ffacf..3441f2b4c0d 100644 --- a/e2e/various-suite/query-editor.spec.ts +++ b/e2e/various-suite/query-editor.spec.ts @@ -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'); diff --git a/jest.config.js b/jest.config.js index 17579560e6d..14b6e1084f3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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': '/public/test/mocks/svg.ts', '\\.css': '/public/test/mocks/style.ts', 'react-inlinesvg': '/public/test/mocks/react-inlinesvg.tsx', - 'monaco-editor/esm/vs/editor/editor.api': '/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': '/public/test/mocks/nearMembraneDom.ts', '^@grafana/schema/dist/esm/(.*)$': '/packages/grafana-schema/src/$1', diff --git a/packages/grafana-data/src/monaco/languageRegistry.ts b/packages/grafana-data/src/monaco/languageRegistry.ts index fcfc843ab31..f12cca29320 100644 --- a/packages/grafana-data/src/monaco/languageRegistry.ts +++ b/packages/grafana-data/src/monaco/languageRegistry.ts @@ -4,7 +4,7 @@ import { Registry, RegistryItem } from '../utils/Registry'; * @alpha */ export interface MonacoLanguageRegistryItem extends RegistryItem { - init: () => Promise; + init: () => Worker; } /** diff --git a/packages/grafana-runtime/src/utils/plugin.ts b/packages/grafana-runtime/src/utils/plugin.ts index ce2ab40b5b3..9a368e84968 100644 --- a/packages/grafana-runtime/src/utils/plugin.ts +++ b/packages/grafana-runtime/src/utils/plugin.ts @@ -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; diff --git a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx index a7ac0d7fb0d..7ab4854ec2c 100644 --- a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx @@ -119,14 +119,12 @@ class UnthemedCodeEditor extends PureComponent { } }); - const languagePromise = this.loadCustomLanguage(); - if (onChange) { editor.getModel()?.onDidChangeContent(() => onChange(editor.getValue())); } if (onEditorDidMount) { - languagePromise.then(() => onEditorDidMount(editor, monaco)); + onEditorDidMount(editor, monaco); } }; diff --git a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx index a6db1ce5fc3..0f0ab1b3ee3 100644 --- a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx @@ -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 ( - + ); }; diff --git a/public/app/app.ts b/public/app/app.ts index cb31f9c58f9..30229d999cc 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -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()); diff --git a/public/app/core/monacoEnv.ts b/public/app/core/monacoEnv.ts new file mode 100644 index 00000000000..db63c867a1e --- /dev/null +++ b/public/app/core/monacoEnv.ts @@ -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)); + }, + }; +} diff --git a/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx b/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx index bb9fe55c4a4..598a95f867a 100644 --- a/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx +++ b/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx @@ -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)); diff --git a/public/app/features/plugins/loader/constants.ts b/public/app/features/plugins/loader/constants.ts index daf840dff2c..0bcc4f9737e 100644 --- a/public/app/features/plugins/loader/constants.ts +++ b/public/app/features/plugins/loader/constants.ts @@ -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]*\))/; diff --git a/public/app/features/plugins/loader/pluginLoader.mock.ts b/public/app/features/plugins/loader/pluginLoader.mock.ts index c683090ad6c..b7433716563 100644 --- a/public/app/features/plugins/loader/pluginLoader.mock.ts +++ b/public/app/features/plugins/loader/pluginLoader.mock.ts @@ -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', - }, - }) ) ); diff --git a/public/app/features/plugins/loader/sharedDependencies.ts b/public/app/features/plugins/loader/sharedDependencies.ts index d1c29ba9b3f..3f9c3d10349 100644 --- a/public/app/features/plugins/loader/sharedDependencies.ts +++ b/public/app/features/plugins/loader/sharedDependencies.ts @@ -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 = { '@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, diff --git a/public/app/features/plugins/loader/systemjsHooks.test.ts b/public/app/features/plugins/loader/systemjsHooks.test.ts index 9cd499b0c04..a0da31252ab 100644 --- a/public/app/features/plugins/loader/systemjsHooks.test.ts +++ b/public/app/features/plugins/loader/systemjsHooks.test.ts @@ -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'; diff --git a/public/app/features/plugins/loader/systemjsHooks.ts b/public/app/features/plugins/loader/systemjsHooks.ts index 8bdbd723b11..fd8ac2e10ee 100644 --- a/public/app/features/plugins/loader/systemjsHooks.ts +++ b/public/app/features/plugins/loader/systemjsHooks.ts @@ -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);`; -} diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx index 2311c733422..2c978f0a8f5 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx @@ -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(); @@ -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); } diff --git a/public/app/plugins/datasource/parca/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/parca/QueryEditor/QueryEditor.test.tsx index 8837006aed5..d9bcac8a2f1 100644 --- a/public/app/plugins/datasource/parca/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/parca/QueryEditor/QueryEditor.test.tsx @@ -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(); diff --git a/public/app/plugins/panel/nodeGraph/layout.worker.js b/public/app/plugins/panel/nodeGraph/layout.worker.js index 205289a1fcd..d0fafa0c54f 100644 --- a/public/app/plugins/panel/nodeGraph/layout.worker.js +++ b/public/app/plugins/panel/nodeGraph/layout.worker.js @@ -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, - }, - }; -} diff --git a/public/app/plugins/panel/nodeGraph/layout.worker.utils.js b/public/app/plugins/panel/nodeGraph/layout.worker.utils.js new file mode 100644 index 00000000000..9c30af6a1ab --- /dev/null +++ b/public/app/plugins/panel/nodeGraph/layout.worker.utils.js @@ -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, + }, + }; +} diff --git a/public/lib/monaco-languages/kusto.ts b/public/lib/monaco-languages/kusto.ts index 92adf5f801e..27a10c4a83a 100644 --- a/public/lib/monaco-languages/kusto.ts +++ b/public/lib/monaco-languages/kusto.ts @@ -1,5 +1,5 @@ +import { CorsWorker as Worker } from 'app/core/utils/CorsWorker'; + export default function loadKusto() { - return new Promise((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)); } diff --git a/public/test/mocks/monaco.ts b/public/test/mocks/monaco.ts deleted file mode 100644 index 2de6c5f4a29..00000000000 --- a/public/test/mocks/monaco.ts +++ /dev/null @@ -1 +0,0 @@ -export const monaco = 'monaco'; diff --git a/public/test/mocks/workers.ts b/public/test/mocks/workers.ts index ddba6e1e386..34ae57a5864 100644 --- a/public/test/mocks/workers.ts +++ b/public/test/mocks/workers.ts @@ -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; diff --git a/public/test/setupTests.ts b/public/test/setupTests.ts index b345588c355..650614f5123 100644 --- a/public/test/setupTests.ts +++ b/public/test/setupTests.ts @@ -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 }); diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index c9db4d4820a..b3c9cc178e3 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -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: [ diff --git a/yarn.lock b/yarn.lock index dff382da06f..b7d742e7a28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" From dbbbfa282ded4c751ff1792a85cab885afe8d0f4 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 22 Feb 2024 06:14:53 -0600 Subject: [PATCH 5/7] StateTimeline: Properly type tooltip prop, remove ts-ignore (#83189) --- public/app/core/components/TimelineChart/TimelineChart.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/TimelineChart/TimelineChart.tsx b/public/app/core/components/TimelineChart/TimelineChart.tsx index 9fb1c4ed748..c22e3230dac 100644 --- a/public/app/core/components/TimelineChart/TimelineChart.tsx +++ b/public/app/core/components/TimelineChart/TimelineChart.tsx @@ -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 { // 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, }); }; From 74c0463cd347e8b7a239dbb36ec6a2ef4926bc8e Mon Sep 17 00:00:00 2001 From: Marie Cruz Date: Thu, 22 Feb 2024 12:27:29 +0000 Subject: [PATCH 6/7] Docs: update grafana fundamentals (#83075) * docs: update grafana fundamentals * fix: lint issues * docs: added more content on grafana fundamentals tutorial --- .../tutorials/grafana-fundamentals/index.md | 87 ++++++++++++++----- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/docs/sources/tutorials/grafana-fundamentals/index.md b/docs/sources/tutorials/grafana-fundamentals/index.md index 1aa2197e4ee..dc7a25d6096 100644 --- a/docs/sources/tutorials/grafana-fundamentals/index.md +++ b/docs/sources/tutorials/grafana-fundamentals/index.md @@ -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. + +
+ +
+ {{% 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. - - +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: From 3ba33fe27834cd62f53799945c5d65fe3fdd54a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Thu, 22 Feb 2024 14:22:14 +0100 Subject: [PATCH 7/7] mysql: do not use unexported grafana-core config (#83062) * mysql: do not use unexported grafana-core config * updated test --- .../plugins_integration_test.go | 2 +- pkg/tsdb/mysql/macros.go | 5 ++- pkg/tsdb/mysql/macros_test.go | 3 +- pkg/tsdb/mysql/mysql.go | 33 +++++++++++-------- pkg/tsdb/mysql/mysql_test.go | 5 ++- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/pkg/services/pluginsintegration/plugins_integration_test.go b/pkg/services/pluginsintegration/plugins_integration_test.go index 778d0c13014..d7d82018ca7 100644 --- a/pkg/services/pluginsintegration/plugins_integration_test.go +++ b/pkg/services/pluginsintegration/plugins_integration_test.go @@ -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) diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index 4cff452e413..05e93c0aee5 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -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, } } diff --git a/pkg/tsdb/mysql/macros_test.go b/pkg/tsdb/mysql/macros_test.go index d8babbfbc38..595e06c7dc4 100644 --- a/pkg/tsdb/mysql/macros_test.go +++ b/pkg/tsdb/mysql/macros_test.go @@ -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{}, } diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 91ed62cd2b8..2e908afc93d 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -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) } } diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index c41766db6c6..c01ec8bae6c 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -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) {