Chore: Upgrade to react 18 (#64428)

* update react 18 related deps

* fix some types

* make sure we're on react-router-dom >= 5.3.3

* Use new root API

* Remove StrictMode for now - react 18 double rendering causes issues

* fix + ignore some @grafana/ui types

* fix some more types

* use renderHook from @testing-library/react in almost all cases

* fix storybook types

* rewrite useDashboardSave to not use useEffect

* make props optional

* only render if props are provided

* add correct type for useCallback

* make resourcepicker tests more robust

* fix ModalManager rendering

* fix some more unit tests

* store the click coordinates in a ref as setState is NOT synchronous

* fix remaining e2e tests

* rewrite dashboardpage tests to avoid act warnings

* undo lint ignores

* fix ExpanderCell types

* set SymbolCell type correctly

* fix QueryAndExpressionsStep

* looks like the types were actually wrong instead :D

* undo this for now...

* remove spinner waits

* more robust tests

* rewrite errorboundary test to not explicitly count the number of renders

* make urlParam expect async

* increase timeout in waitFor

* revert ExplorePage test changes

* Update public/app/features/dashboard/containers/DashboardPage.test.tsx

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* Update public/app/features/dashboard/containers/PublicDashboardPage.test.tsx

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* Update public/app/features/dashboard/containers/PublicDashboardPage.test.tsx

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* Update public/app/features/dashboard/containers/PublicDashboardPage.test.tsx

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* skip fakeTimer test, ignore table types for now + other review comments

* update package peerDeps

* small tweak to resourcepicker test

* update lockfile...

* increase timeout in sharepublicdashboard tests

* ensure ExplorePaneContainer passes correct queries to initializeExplore

* fix LokiContextUI test

* fix unit tests

* make importDashboard flow more consistent

* wait for dashboard name before continuing

* more test fixes

* readd dashboard name to variable e2e tests

* wait for switches to be enabled before clicking

* fix modal rendering

* don't use @testing-library/dom directly

* quick fix for rendering of panels in firefox

* make PromQueryField test more robust

* don't wait for chartData - in react 18 this can happen before the wait code even gets executed

---------

Co-authored-by: kay delaney <kay@grafana.com>
Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
Ashley Harrison 2023-04-11 10:51:54 +01:00 committed by GitHub
parent a5499bbf70
commit 1261345b81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 921 additions and 920 deletions

View File

@ -22,16 +22,6 @@
"@sentry/browser",
"@sentry/types",
"@sentry/utils",
// dep updates blocked by React 18
"@testing-library/dom",
"@testing-library/react",
"@types/react",
"@types/react-dom",
"@types/react-test-renderer",
"react",
"react-dom",
"react-test-renderer"
],
"includePaths": ["package.json", "packages/**"],
"ignorePaths": ["packages/grafana-toolkit/package.json", "emails/**", "plugins-bundled/**", "**/mocks/**"],

View File

@ -0,0 +1,14 @@
diff --git a/dist/ts3.9/blocks/DocsContainer.d.ts b/dist/ts3.9/blocks/DocsContainer.d.ts
index be330e44bebb02eaf2c92d365d4e7dc1da452465..6c8b1d42bea2e184456e2757eb2ee20076ba43b3 100644
--- a/dist/ts3.9/blocks/DocsContainer.d.ts
+++ b/dist/ts3.9/blocks/DocsContainer.d.ts
@@ -1,7 +1,8 @@
-import { FunctionComponent } from 'react';
+import { FunctionComponent, ReactNode } from 'react';
import { AnyFramework } from '@storybook/csf';
import { DocsContextProps } from './DocsContext';
export interface DocsContainerProps<TFramework extends AnyFramework = AnyFramework> {
context: DocsContextProps<TFramework>;
+ children?: ReactNode;
}
export declare const DocsContainer: FunctionComponent<DocsContainerProps>;

View File

@ -0,0 +1,12 @@
diff --git a/index.d.ts b/index.d.ts
index d116f54d6da12d24b48e24ff3636c9066059aa58..93290945d8b1818cab893d6466179b33869a47b9 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -25,6 +25,7 @@ export type SplitPaneProps = {
pane2Style?: React.CSSProperties;
resizerClassName?: string;
step?: number;
+ children?: React.ReactNode;
};
export type SplitPaneState = {

View File

@ -1,5 +1,6 @@
import { e2e } from '@grafana/e2e';
const PAGE_UNDER_TEST = 'k3PEoCpnk/repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel';
const DASHBOARD_NAME = 'Repeating a row with a non-repeating panel and horizontal repeating panel';
describe('Repeating a row with repeated panels and a non-repeating panel', () => {
beforeEach(() => {
@ -8,6 +9,7 @@ describe('Repeating a row with repeated panels and a non-repeating panel', () =>
it('should be able to collapse and expand a repeated row without losing panels', () => {
e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST });
e2e().contains(DASHBOARD_NAME).should('be.visible');
const panelsToCheck = [
'Row 2 non-repeating panel',

View File

@ -2,11 +2,13 @@ import { e2e } from '@grafana/e2e';
import { GrafanaBootConfig } from '@grafana/runtime';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
const DASHBOARD_NAME = 'Test variable output';
describe('Variables - Constant', () => {
it('can add a new constant variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
// Create a new "Constant" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
@ -15,8 +17,8 @@ describe('Variables - Constant', () => {
e2e().get('input').type('Constant{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInputV2().type('pesto').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().eq(0).should('have.text', 'pesto');

View File

@ -2,6 +2,7 @@ import { e2e } from '@grafana/e2e';
import { GrafanaBootConfig } from '@grafana/runtime';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
const DASHBOARD_NAME = 'Test variable output';
function fillInCustomVariable(name: string, label: string, value: string) {
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
@ -23,6 +24,7 @@ describe('Variables - Custom', () => {
it('can add a custom template variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
// Create a new "Custom" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
@ -50,6 +52,7 @@ describe('Variables - Custom', () => {
it('can add a custom template variable with labels', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
// Create a new "Custom" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();

View File

@ -2,11 +2,13 @@ import { e2e } from '@grafana/e2e';
import { GrafanaBootConfig } from '@grafana/runtime';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
const DASHBOARD_NAME = 'Test variable output';
describe('Variables - Datasource', () => {
it('can add a new datasource variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
// Create a new "Datasource" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();

View File

@ -2,6 +2,7 @@ import { e2e } from '@grafana/e2e';
import { GrafanaBootConfig } from '@grafana/runtime';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
const DASHBOARD_NAME = 'Test variable output';
function assertPreviewValues(expectedValues: string[]) {
for (const expected of expectedValues) {
@ -14,6 +15,7 @@ describe('Variables - Interval', () => {
it('can add a new interval variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
// Create a new "Interval" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();

View File

@ -2,11 +2,13 @@ import { e2e } from '@grafana/e2e';
import { GrafanaBootConfig } from '@grafana/runtime';
const PAGE_UNDER_TEST = '-Y-tnEDWk/templating-nested-template-variables';
const DASHBOARD_NAME = 'Templating - Nested Template Variables';
describe('Variables - Query - Add variable', () => {
it('query variable should be default and default fields should be correct', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
e2e.pages.Dashboard.Settings.Variables.List.newButton().should('be.visible').click();
@ -77,6 +79,7 @@ describe('Variables - Query - Add variable', () => {
it('adding a single value query variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
e2e.pages.Dashboard.Settings.Variables.List.newButton().should('be.visible').click();
@ -132,6 +135,7 @@ describe('Variables - Query - Add variable', () => {
it('adding a multi value query variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
e2e.pages.Dashboard.Settings.Variables.List.newButton().should('be.visible').click();

View File

@ -2,11 +2,13 @@ import { e2e } from '@grafana/e2e';
import { GrafanaBootConfig } from '@grafana/runtime';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
const DASHBOARD_NAME = 'Test variable output';
describe('Variables - Text box', () => {
it('can add a new text box variable', () => {
e2e.flows.login('admin', 'admin');
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
e2e().contains(DASHBOARD_NAME).should('be.visible');
// Create a new "text box" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();

View File

@ -29,7 +29,9 @@ describe('Trace view', () => {
e2e.pages.Explore.General.scrollView().children('.scrollbar-view').scrollTo('center');
// After scrolling we should load more spans
e2e.components.TraceViewer.spanBar().its('length').should('be.gt', oldLength);
e2e.components.TraceViewer.spanBar().should(($span) => {
expect($span.length).to.be.gt(oldLength);
});
});
});
});

View File

@ -114,10 +114,9 @@
"@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1",
"@swc/core": "1.3.38",
"@swc/helpers": "0.4.14",
"@testing-library/dom": "8.20.0",
"@testing-library/dom": "9.0.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.4",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@types/angular": "1.8.4",
"@types/angular-route": "1.7.2",
@ -146,15 +145,15 @@
"@types/papaparse": "5.3.7",
"@types/pluralize": "^0.0.29",
"@types/prismjs": "1.26.0",
"@types/react": "17.0.42",
"@types/react": "18.0.28",
"@types/react-beautiful-dnd": "13.1.3",
"@types/react-dom": "17.0.14",
"@types/react-dom": "18.0.11",
"@types/react-grid-layout": "1.3.2",
"@types/react-highlight-words": "0.16.4",
"@types/react-redux": "7.1.25",
"@types/react-router-dom": "5.3.3",
"@types/react-table": "7.7.14",
"@types/react-test-renderer": "17.0.1",
"@types/react-test-renderer": "18.0.0",
"@types/react-transition-group": "4.4.5",
"@types/react-virtualized-auto-sizer": "1.0.1",
"@types/react-window": "1.8.5",
@ -226,7 +225,7 @@
"react-refresh": "0.14.0",
"react-select-event": "5.5.1",
"react-simple-compat": "1.2.3",
"react-test-renderer": "17.0.2",
"react-test-renderer": "18.2.0",
"redux-mock-store": "1.5.4",
"rimraf": "4.4.0",
"rudder-sdk-js": "2.25.0",
@ -294,6 +293,7 @@
"@sentry/browser": "6.19.7",
"@sentry/types": "6.19.7",
"@sentry/utils": "6.19.7",
"@testing-library/react-hooks": "^8.0.1",
"@types/react-resizable": "3.0.3",
"@types/webpack-env": "1.18.0",
"@visx/event": "3.0.1",
@ -366,11 +366,11 @@
"rc-time-picker": "3.7.3",
"rc-tree": "5.7.2",
"re-resizable": "6.9.9",
"react": "17.0.2",
"react": "18.2.0",
"react-awesome-query-builder": "5.4.0",
"react-beautiful-dnd": "13.1.1",
"react-diff-viewer": "^3.1.1",
"react-dom": "17.0.2",
"react-dom": "18.2.0",
"react-draggable": "4.4.5",
"react-dropzone": "^14.2.3",
"react-enable": "^3.1.0",
@ -385,7 +385,7 @@
"react-redux": "7.2.6",
"react-resizable": "3.0.4",
"react-reverse-portal": "2.1.1",
"react-router-dom": "^5.2.0",
"react-router-dom": "5.3.3",
"react-select": "5.7.0",
"react-split-pane": "0.1.92",
"react-table": "7.8.0",
@ -437,7 +437,9 @@
"@storybook/manager-webpack5/webpack": "5.76.0",
"ngtemplate-loader/loader-utils": "^2.0.0",
"trim": "0.0.3",
"slate-dev-environment@^0.2.2": "patch:slate-dev-environment@npm:0.2.5#.yarn/patches/slate-dev-environment-npm-0.2.5-9aeb7da7b5.patch"
"slate-dev-environment@^0.2.2": "patch:slate-dev-environment@npm:0.2.5#.yarn/patches/slate-dev-environment-npm-0.2.5-9aeb7da7b5.patch",
"react-split-pane@0.1.92": "patch:react-split-pane@npm:0.1.92#.yarn/patches/react-split-pane-npm-0.1.92-93dbf51dff.patch",
"@storybook/addon-docs@6.5.16": "patch:@storybook/addon-docs@npm:6.5.16#.yarn/patches/@storybook-addon-docs-npm-6.5.16-56ecbd77e7.patch"
},
"workspaces": {
"packages": [

View File

@ -63,10 +63,9 @@
"@rollup/plugin-commonjs": "23.0.2",
"@rollup/plugin-json": "5.0.1",
"@rollup/plugin-node-resolve": "15.0.1",
"@testing-library/dom": "8.20.0",
"@testing-library/dom": "9.0.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.4",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@types/dompurify": "^2",
"@types/history": "4.7.11",
@ -76,15 +75,15 @@
"@types/marked": "4.0.8",
"@types/node": "18.14.6",
"@types/papaparse": "5.3.7",
"@types/react": "17.0.42",
"@types/react-dom": "17.0.14",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/sinon": "10.0.13",
"@types/testing-library__jest-dom": "5.14.5",
"@types/tinycolor2": "1.4.3",
"esbuild": "0.16.17",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-test-renderer": "17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-test-renderer": "18.2.0",
"rimraf": "4.4.0",
"rollup": "2.79.1",
"rollup-plugin-dts": "^5.0.0",
@ -94,7 +93,7 @@
"typescript": "4.8.4"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
}

View File

@ -141,7 +141,6 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC
if (queriesForm) {
queriesForm(fullConfig);
e2e().wait('@chartData');
// Wait for a possible complex visualization to render (or something related, as this isn't necessary on the dashboard page)
// Can't assert that its HTML changed because a new query could produce the same results
@ -158,8 +157,6 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC
// Avoid annotations flakiness
e2e.components.RefreshPicker.runButtonV2().first().click({ force: true });
e2e().wait('@chartData');
// Wait for RxJS
e2e().wait(500);

View File

@ -20,10 +20,9 @@ export const importDashboard = (dashboardToImport: Dashboard, queryTimeout?: num
e2e().visit(fromBaseUrl('/dashboard/import'));
// Note: normally we'd use 'click' and then 'type' here, but the json object is so big that using 'val' is much faster
e2e.components.DashboardImportPage.textarea()
.should('be.visible')
.click()
.invoke('val', JSON.stringify(dashboardToImport));
e2e.components.DashboardImportPage.textarea().should('be.visible');
e2e.components.DashboardImportPage.textarea().click();
e2e.components.DashboardImportPage.textarea().invoke('val', JSON.stringify(dashboardToImport));
e2e.components.DashboardImportPage.submit().should('be.visible').click();
e2e.components.ImportDashboardForm.name().should('be.visible').click().clear().type(dashboardToImport.title);
e2e.components.ImportDashboardForm.submit().should('be.visible').click();

View File

@ -52,21 +52,20 @@
"@grafana/tsconfig": "^1.2.0-rc1",
"@rollup/plugin-commonjs": "23.0.2",
"@rollup/plugin-node-resolve": "15.0.1",
"@testing-library/dom": "8.20.0",
"@testing-library/react": "12.1.4",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/dom": "9.0.1",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@types/angular": "1.8.4",
"@types/history": "4.7.11",
"@types/jest": "29.2.3",
"@types/lodash": "4.14.191",
"@types/react": "17.0.42",
"@types/react-dom": "17.0.14",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/systemjs": "^0.20.6",
"esbuild": "0.16.17",
"lodash": "4.17.21",
"react": "17.0.2",
"react-dom": "17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"rimraf": "4.4.0",
"rollup": "2.79.1",
"rollup-plugin-dts": "^5.0.0",
@ -77,7 +76,7 @@
"typescript": "4.8.4"
},
"peerDependencies": {
"react": "17.0.2",
"react-dom": "17.0.2"
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
}

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import React from 'react';
import {

View File

@ -97,7 +97,7 @@
"react-inlinesvg": "3.0.2",
"react-popper": "2.3.0",
"react-popper-tooltip": "4.4.2",
"react-router-dom": "^5.2.0",
"react-router-dom": "5.3.3",
"react-select": "5.7.0",
"react-select-event": "^5.1.0",
"react-table": "7.8.0",
@ -134,10 +134,9 @@
"@storybook/preset-scss": "1.0.3",
"@storybook/react": "6.5.16",
"@storybook/theming": "6.5.16",
"@testing-library/dom": "8.20.0",
"@testing-library/dom": "9.0.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.4",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@types/common-tags": "^1.8.0",
"@types/d3": "7.4.0",
@ -149,15 +148,15 @@
"@types/mock-raf": "1.0.3",
"@types/node": "18.14.6",
"@types/prismjs": "1.26.0",
"@types/react": "17.0.42",
"@types/react": "18.0.28",
"@types/react-beautiful-dnd": "13.1.3",
"@types/react-calendar": "3.9.0",
"@types/react-color": "3.0.6",
"@types/react-dom": "17.0.14",
"@types/react-dom": "18.0.11",
"@types/react-highlight-words": "0.16.4",
"@types/react-router-dom": "5.3.3",
"@types/react-table": "7.7.14",
"@types/react-test-renderer": "17.0.1",
"@types/react-test-renderer": "18.0.0",
"@types/react-transition-group": "4.4.5",
"@types/react-window": "1.8.5",
"@types/slate": "0.47.11",
@ -173,9 +172,9 @@
"expose-loader": "4.0.0",
"mock-raf": "1.0.1",
"process": "^0.11.10",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-test-renderer": "17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-test-renderer": "18.2.0",
"rimraf": "4.4.0",
"rollup": "2.79.1",
"rollup-plugin-dts": "^5.0.0",
@ -190,7 +189,7 @@
"webpack": "5.76.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
}

View File

@ -71,7 +71,7 @@ export const Dropdown = React.memo(({ children, overlay, placement, offset, onVi
timeout={{ appear: animationDuration, exit: 0, enter: 0 }}
classNames={animationStyles}
>
<div ref={transitionRef}>{ReactUtils.renderOrCallToRender(overlay)}</div>
<div ref={transitionRef}>{ReactUtils.renderOrCallToRender(overlay, {})}</div>
</CSSTransition>
</div>
</FocusScope>

View File

@ -57,7 +57,7 @@ describe('ErrorBoundary', () => {
expect((faro.api.pushError as jest.Mock).mock.calls[0][0]).toBe(problem);
});
it('should recover when when recover props change', async () => {
it('should rerender when recover props change', async () => {
const problem = new Error('things went terribly wrong');
let renderCount = 0;
@ -75,6 +75,8 @@ describe('ErrorBoundary', () => {
);
await screen.findByText(problem.message);
expect(renderCount).toBeGreaterThan(0);
const oldRenderCount = renderCount;
rerender(
<ErrorBoundary dependencies={[1, 3]}>
@ -89,6 +91,6 @@ describe('ErrorBoundary', () => {
</ErrorBoundary>
);
expect(renderCount).toBe(2);
expect(renderCount).toBeGreaterThan(oldRenderCount);
});
});

View File

@ -10,7 +10,7 @@ const expanderContainerStyles = css`
height: 100%;
`;
export function ExpanderCell<K extends object>({ row, __rowID }: CellProps<K, void> & { __rowID: string }) {
export function ExpanderCell<K extends object>({ row, __rowID }: CellProps<K, void>) {
return (
<div className={expanderContainerStyles}>
<IconButton

View File

@ -158,13 +158,14 @@ export const LogRowContextGroup = ({
<List
items={rows}
renderItem={(item) => {
const message = typeof item === 'string' ? item : item.message ?? '';
return (
<div
className={css`
padding: 5px 0;
`}
>
{typeof item === 'string' && textUtil.hasAnsiCodes(item) ? <LogMessageAnsi value={item} /> : item}
{textUtil.hasAnsiCodes(message) ? <LogMessageAnsi value={message} /> : message}
</div>
);
}}

View File

@ -1,5 +1,4 @@
import { fireEvent } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';

View File

@ -1,6 +1,4 @@
import { fireEvent } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react-hooks';
import { act, fireEvent, render, renderHook, screen } from '@testing-library/react';
import React, { createRef, KeyboardEvent, RefObject } from 'react';
import { useMenuFocus } from './hooks';

View File

@ -40,6 +40,7 @@ export function Segment<T>({
if (!expanded) {
const label = isObject(value) ? value.label : value;
const labelAsString = label != null ? String(label) : undefined;
return (
<Label
@ -56,7 +57,7 @@ export function Segment<T>({
className
)}
>
{label || placeholder}
{labelAsString || placeholder}
</InlineLabel>
)
}

View File

@ -53,6 +53,7 @@ export function SegmentAsync<T>({
if (!expanded) {
const label = isObject(value) ? value.label : value;
const labelAsString = label != null ? String(label) : undefined;
return (
<Label
@ -70,7 +71,7 @@ export function SegmentAsync<T>({
className
)}
>
{label || placeholder}
{labelAsString || placeholder}
</InlineLabel>
)
}

View File

@ -17,7 +17,7 @@ const HandleTooltip = (props: {
children: React.ReactElement;
visible: boolean;
placement: 'top' | 'right';
tipFormatter?: (value: number) => React.ReactNode;
tipFormatter?: () => React.ReactNode;
}) => {
const { value, children, visible, placement, tipFormatter, ...restProps } = props;
@ -71,6 +71,7 @@ const tooltipStyles = (theme: GrafanaTheme2) => {
fontSize: theme.typography.bodySmall.fontSize,
opacity: 0.9,
padding: 3,
zIndex: theme.zIndex.tooltip,
}),
};
};

View File

@ -51,7 +51,7 @@ export const RangeSlider = ({
<HandleTooltip
value={handleProps.value}
visible={tooltipAlwaysVisible || handleProps.dragging}
tipFormatter={formatTooltipResult}
tipFormatter={formatTooltipResult ? () => formatTooltipResult(handleProps.value) : undefined}
placement={isHorizontal ? 'top' : 'right'}
>
{node}

View File

@ -85,6 +85,8 @@ export function getColumns(
// Make an expander cell
Header: () => null, // No header
id: 'expander', // It needs an ID
// @ts-expect-error
// TODO fix type error here
Cell: RowExpander,
width: EXPANDER_WIDTH,
minWidth: EXPANDER_WIDTH,
@ -124,6 +126,8 @@ export function getColumns(
const Cell = getCellComponent(fieldTableOptions.cellOptions?.type, field);
columns.push({
// @ts-expect-error
// TODO fix type error here
Cell,
id: fieldIndex.toString(),
field: field,

View File

@ -1,6 +1,5 @@
import { css } from '@emotion/css';
import { render } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { render, renderHook } from '@testing-library/react';
import React from 'react';
import { mockThemeContext, useStyles2 } from './ThemeContext';

View File

@ -25,14 +25,14 @@ export function getChildId(children: ReactElement): string | undefined {
* @param props props to be passed to the function if item provided as such
*/
export function renderOrCallToRender<TProps = {}>(
itemToRender: ((props?: TProps) => React.ReactNode) | React.ReactNode,
itemToRender: ((props: TProps) => React.ReactNode) | React.ReactNode,
props?: TProps
): React.ReactNode {
if (React.isValidElement(itemToRender) || typeof itemToRender === 'string' || typeof itemToRender === 'number') {
return itemToRender;
}
if (typeof itemToRender === 'function') {
if (typeof itemToRender === 'function' && props) {
return itemToRender(props);
}

View File

@ -18,17 +18,17 @@
"@grafana/toolkit": "10.0.0-pre",
"@types/jest": "26.0.15",
"@types/lodash": "4.14.149",
"@types/react": "17.0.30",
"@types/react": "18.0.28",
"lodash": "4.17.21"
},
"dependencies": {
"@grafana/data": "10.0.0-pre",
"@grafana/ui": "10.0.0-pre",
"jquery": "3.5.1",
"react": "17.0.1",
"react-dom": "17.0.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "7.5.3",
"react-router-dom": "^5.2.0",
"react-router-dom": "5.3.3",
"tslib": "2.4.0"
}
}

View File

@ -94,42 +94,40 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
};
return (
<React.StrictMode>
<Provider store={store}>
<ErrorBoundaryAlert style="page">
<GrafanaContext.Provider value={app.context}>
<ThemeProvider value={config.theme2}>
<KBarProvider
actions={[]}
options={{ enableHistory: true, callbacks: { onSelectAction: commandPaletteActionSelected } }}
>
<ModalsProvider>
<GlobalStyles />
<div className="grafana-app">
<Router history={locationService.getHistory()}>
<AppChrome>
{pageBanners.map((Banner, index) => (
<Banner key={index.toString()} />
))}
<AngularRoot />
<AppNotificationList />
{ready && this.renderRoutes()}
{bodyRenderHooks.map((Hook, index) => (
<Hook key={index.toString()} />
))}
</AppChrome>
</Router>
</div>
<LiveConnectionWarning />
<ModalRoot />
<PortalContainer />
</ModalsProvider>
</KBarProvider>
</ThemeProvider>
</GrafanaContext.Provider>
</ErrorBoundaryAlert>
</Provider>
</React.StrictMode>
<Provider store={store}>
<ErrorBoundaryAlert style="page">
<GrafanaContext.Provider value={app.context}>
<ThemeProvider value={config.theme2}>
<KBarProvider
actions={[]}
options={{ enableHistory: true, callbacks: { onSelectAction: commandPaletteActionSelected } }}
>
<ModalsProvider>
<GlobalStyles />
<div className="grafana-app">
<Router history={locationService.getHistory()}>
<AppChrome>
{pageBanners.map((Banner, index) => (
<Banner key={index.toString()} />
))}
<AngularRoot />
<AppNotificationList />
{ready && this.renderRoutes()}
{bodyRenderHooks.map((Hook, index) => (
<Hook key={index.toString()} />
))}
</AppChrome>
</Router>
</div>
<LiveConnectionWarning />
<ModalRoot />
<PortalContainer />
</ModalsProvider>
</KBarProvider>
</ThemeProvider>
</GrafanaContext.Provider>
</ErrorBoundaryAlert>
</Provider>
);
}
}

View File

@ -12,14 +12,17 @@
import angular, { auto } from 'angular';
import { kebabCase } from 'lodash';
import React, { ComponentType } from 'react';
import ReactDOM from 'react-dom';
import { createRoot, Root } from 'react-dom/client';
// get a react component from name (components can be an angular injectable e.g. value, factory or
// available on window
function getReactComponent(name: string | Function, $injector: auto.IInjectorService): ComponentType {
function getReactComponent(
name: string | Function,
$injector: auto.IInjectorService
): ComponentType<React.PropsWithChildren<{}>> {
// if name is a function assume it is component and return it
if (angular.isFunction(name)) {
return name as unknown as ComponentType;
return name as unknown as ComponentType<React.PropsWithChildren<{}>>;
}
// a React component name must be specified
@ -46,7 +49,7 @@ function getReactComponent(name: string | Function, $injector: auto.IInjectorSer
throw Error('Cannot find react component ' + name);
}
return reactComponent as unknown as ComponentType;
return reactComponent as unknown as ComponentType<React.PropsWithChildren<{}>>;
}
// wraps a function with scope.$apply, if already applied just return
@ -140,9 +143,9 @@ function watchProps(watchDepth: string, scope: any, watchExpressions: any[], lis
}
// render React component, with scope[attrs.props] being passed in as the component props
function renderComponent(component: any, props: object, scope: any, elem: Element[]) {
function renderComponent(component: any, props: object, scope: any, root: Root) {
scope.$evalAsync(() => {
ReactDOM.render(React.createElement(component, props), elem[0]);
root.render(React.createElement(component, props));
});
}
@ -207,11 +210,12 @@ const reactComponent = ($injector: any): any => {
link: function (scope: any, elem: Element[], attrs: any) {
const reactComponent = getReactComponent(attrs.name, $injector);
const root = createRoot(elem[0]);
const renderMyComponent = () => {
const scopeProps = scope.$eval(attrs.props);
const props = applyFunctions(scopeProps, scope);
renderComponent(reactComponent, props, scope, elem);
renderComponent(reactComponent, props, scope, root);
};
// If there are props, re-render when they change
@ -220,10 +224,10 @@ const reactComponent = ($injector: any): any => {
// cleanup when scope is destroyed
scope.$on('$destroy', () => {
if (!attrs.onScopeDestroy) {
ReactDOM.unmountComponentAtNode(elem[0]);
root.unmount();
} else {
scope.$eval(attrs.onScopeDestroy, {
unmountComponent: ReactDOM.unmountComponentAtNode.bind(this, elem[0]),
unmountComponent: root.unmount.bind(this),
});
}
});
@ -264,6 +268,7 @@ const reactDirective = ($injector: auto.IInjectorService) => {
replace: true,
link: function (scope: any, elem: Element[], attrs: any) {
const reactComponent = getReactComponent(reactComponentName, $injector);
const root = createRoot(elem[0]);
// if props is not defined, fall back to use the React component's propTypes if present
props = props || Object.keys(reactComponent.propTypes || {});
@ -281,7 +286,7 @@ const reactDirective = ($injector: auto.IInjectorService) => {
scopeProps = applyFunctions(scopeProps, scope, config);
scopeProps = angular.extend({}, scopeProps, injectableProps);
renderComponent(reactComponent, scopeProps, scope, elem);
renderComponent(reactComponent, scopeProps, scope, root);
};
// watch each property name and trigger an update whenever something changes,
@ -298,10 +303,10 @@ const reactDirective = ($injector: auto.IInjectorService) => {
// cleanup when scope is destroyed
scope.$on('$destroy', () => {
if (!attrs.onScopeDestroy) {
ReactDOM.unmountComponentAtNode(elem[0]);
root.unmount();
} else {
scope.$eval(attrs.onScopeDestroy, {
unmountComponent: ReactDOM.unmountComponentAtNode.bind(this, elem[0]),
unmountComponent: root.unmount.bind(this),
});
}
});

View File

@ -11,7 +11,7 @@ import 'app/features/all';
import _ from 'lodash'; // eslint-disable-line lodash/import-scope
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import {
locationUtil,
@ -209,11 +209,11 @@ export class GrafanaApp {
config,
};
ReactDOM.render(
const root = createRoot(document.getElementById('reactRoot')!);
root.render(
React.createElement(AppWrapper, {
app: this,
}),
document.getElementById('reactRoot')
})
);
} catch (error) {
console.error('Failed to start Grafana', error);

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { textUtil } from '@grafana/data';
import { config, CopyPanelEvent } from '@grafana/runtime';
@ -19,6 +19,7 @@ import { provideTheme } from '../utils/ConfigProvider';
export class ModalManager {
reactModalRoot = document.body;
reactModalNode = document.createElement('div');
root = createRoot(this.reactModalNode);
init() {
appEvents.subscribe(ShowConfirmModalEvent, (e) => this.showConfirmModal(e.payload));
@ -39,11 +40,11 @@ export class ModalManager {
const elem = React.createElement(provideTheme(AngularModalProxy, config.theme2), modalProps);
this.reactModalRoot.appendChild(this.reactModalNode);
ReactDOM.render(elem, this.reactModalNode);
this.root.render(elem);
}
onReactModalDismiss = () => {
ReactDOM.unmountComponentAtNode(this.reactModalNode);
this.root.render(null);
this.reactModalRoot.removeChild(this.reactModalNode);
};
@ -96,6 +97,6 @@ export class ModalManager {
const elem = React.createElement(provideTheme(AngularModalProxy, config.theme2), modalProps);
this.reactModalRoot.appendChild(this.reactModalNode);
ReactDOM.render(elem, this.reactModalNode);
this.root.render(elem);
}
}

View File

@ -52,7 +52,7 @@ const NotificationsListPage: FC = () => {
return (
<Page navModel={navModel}>
<Page.Contents>
{state.error && <p>{state.error}</p>}
{state.error && <p>{state.error.message}</p>}
{!!notifications.length && (
<>
<div className="page-action-bar">

View File

@ -1,4 +1,4 @@
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { PanelModel } from '../dashboard/state';
@ -27,13 +27,15 @@ const props: React.ComponentProps<typeof TestRuleResult> = {
};
describe('TestRuleResult', () => {
it('should render without error', () => {
expect(() => render(<TestRuleResult {...props} />)).not.toThrow();
it('should render without error', async () => {
render(<TestRuleResult {...props} />);
await screen.findByRole('button', { name: 'Copy to Clipboard' });
});
it('should call testRule when mounting', () => {
it('should call testRule when mounting', async () => {
jest.spyOn(backendSrv, 'post');
render(<TestRuleResult {...props} />);
await screen.findByRole('button', { name: 'Copy to Clipboard' });
expect(backendSrv.post).toHaveBeenCalledWith(
'/api/alerts/test',

View File

@ -189,7 +189,7 @@ describe('Receivers', () => {
mocks.api.fetchConfig.mockImplementation((name) =>
Promise.resolve(name === GRAFANA_RULES_SOURCE_NAME ? someGrafanaAlertManagerConfig : someCloudAlertManagerConfig)
);
await renderReceivers();
renderReceivers();
// check that by default grafana templates & receivers are fetched rendered in appropriate tables
await ui.receiversTable.find();
@ -234,7 +234,7 @@ describe('Receivers', () => {
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
await renderReceivers();
renderReceivers();
// go to new contact point page
await userEvent.click(await ui.newContactPointButton.find());
@ -292,7 +292,7 @@ describe('Receivers', () => {
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
mocks.api.updateConfig.mockResolvedValue();
await renderReceivers();
renderReceivers();
// go to new contact point page
await userEvent.click(await ui.newContactPointButton.find());
@ -351,18 +351,19 @@ describe('Receivers', () => {
});
});
it('Hides create contact point button for users without permission', () => {
it('Hides create contact point button for users without permission', async () => {
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
mocks.api.updateConfig.mockResolvedValue();
mocks.contextSrv.hasAccess.mockImplementation((action) =>
mocks.contextSrv.hasPermission.mockImplementation((action) =>
[AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsExternalRead].some(
(a) => a === action
)
);
mocks.hooks.useGetContactPointsState.mockReturnValue(emptyContactPointsState);
renderReceivers();
await ui.receiversTable.find();
expect(ui.newContactPointButton.query()).not.toBeInTheDocument();
});
@ -372,7 +373,7 @@ describe('Receivers', () => {
mocks.api.fetchConfig.mockResolvedValue(someCloudAlertManagerConfig);
mocks.api.updateConfig.mockResolvedValue();
await renderReceivers('CloudManager');
renderReceivers('CloudManager');
// click edit button for the receiver
await ui.receiversTable.find();
@ -404,7 +405,7 @@ describe('Receivers', () => {
// delete a field
await userEvent.click(byText(/Fields \(2\)/i).get(slackContainer));
await userEvent.click(byTestId('items.1.settings.fields.0.delete-button').get());
await byText(/Fields \(1\)/i).get(slackContainer);
byText(/Fields \(1\)/i).get(slackContainer);
// add another channel
await userEvent.click(ui.newContactPointIntegrationButton.get());
@ -470,7 +471,7 @@ describe('Receivers', () => {
...someCloudAlertManagerStatus,
config: someCloudAlertManagerConfig.alertmanager_config,
});
await renderReceivers(dataSources.promAlertManager.name);
renderReceivers(dataSources.promAlertManager.name);
await ui.receiversTable.find();
// there's no templates table for vanilla prom, API does not return templates
@ -510,7 +511,7 @@ describe('Receivers', () => {
alertmanager_config: {},
});
mocks.api.fetchStatus.mockResolvedValue(someCloudAlertManagerStatus);
await renderReceivers('CloudManager');
renderReceivers('CloudManager');
// check that receiver from the default config is represented
await ui.receiversTable.find();
@ -530,7 +531,7 @@ describe('Receivers', () => {
mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: true });
mocks.api.fetchConfig.mockRejectedValue({ message: 'alertmanager storage object not found' });
await renderReceivers('CloudManager');
renderReceivers('CloudManager');
const templatesTable = await ui.templatesTable.find();
const receiversTable = await ui.receiversTable.find();
@ -588,7 +589,7 @@ describe('Receivers', () => {
};
mocks.hooks.useGetContactPointsState.mockReturnValue(receiversMock);
await renderReceivers();
renderReceivers();
//
await ui.receiversTable.find();
@ -660,7 +661,7 @@ describe('Receivers', () => {
};
mocks.hooks.useGetContactPointsState.mockReturnValue(receiversMock);
await renderReceivers();
renderReceivers();
//
await ui.receiversTable.find();
@ -696,7 +697,7 @@ describe('Receivers', () => {
mocks.api.updateConfig.mockResolvedValue();
mocks.hooks.useGetContactPointsState.mockReturnValue(emptyContactPointsState);
await renderReceivers();
renderReceivers();
await ui.receiversTable.find();
//should not render notification error

View File

@ -218,9 +218,11 @@ const useAlertGroupsModal = (): [
setMatchers([]);
}, []);
const handleShow = useCallback((alertGroups, matchers) => {
const handleShow = useCallback((alertGroups: AlertmanagerGroup[], matchers?: ObjectMatcher[]) => {
setAlertGroups(alertGroups);
setMatchers(matchers);
if (matchers) {
setMatchers(matchers);
}
setShowModal(true);
}, []);

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
import { MemoryRouter, Router } from 'react-router-dom';

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, waitFor } from '@testing-library/react';
import { setupServer } from 'msw/node';
import React from 'react';
import { Provider } from 'react-redux';
@ -48,15 +48,15 @@ describe('useExternalDataSourceAlertmanagers', () => {
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
// Act
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitForNextUpdate();
const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitFor(() => {
// Assert
const { current } = result;
// Assert
const { current } = result;
expect(current).toHaveLength(1);
expect(current[0].dataSource.uid).toBe('1');
expect(current[0].url).toBe('http://grafana.com');
expect(current).toHaveLength(1);
expect(current[0].dataSource.uid).toBe('1');
expect(current[0].url).toBe('http://grafana.com');
});
});
it('Should have active state if available in the activeAlertManagers', async () => {
@ -81,15 +81,15 @@ describe('useExternalDataSourceAlertmanagers', () => {
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
// Act
const { result, waitForValueToChange } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitForValueToChange(() => result.current[0].status);
const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitFor(() => {
// Assert
const { current } = result;
// Assert
const { current } = result;
expect(current).toHaveLength(1);
expect(current[0].status).toBe('active');
expect(current[0].statusInconclusive).toBe(false);
expect(current).toHaveLength(1);
expect(current[0].status).toBe('active');
expect(current[0].statusInconclusive).toBe(false);
});
});
it('Should have dropped state if available in the droppedAlertManagers', async () => {
@ -114,15 +114,16 @@ describe('useExternalDataSourceAlertmanagers', () => {
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
// Act
const { result, waitForValueToChange } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitForValueToChange(() => result.current[0].status);
const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
// Assert
const { current } = result;
await waitFor(() => {
// Assert
const { current } = result;
expect(current).toHaveLength(1);
expect(current[0].status).toBe('dropped');
expect(current[0].statusInconclusive).toBe(false);
expect(current).toHaveLength(1);
expect(current[0].status).toBe('dropped');
expect(current[0].statusInconclusive).toBe(false);
});
});
it('Should have pending state if not available neither in dropped nor in active alertManagers', async () => {
@ -147,15 +148,16 @@ describe('useExternalDataSourceAlertmanagers', () => {
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
// Act
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitForNextUpdate();
const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
// Assert
const { current } = result;
await waitFor(() => {
// Assert
const { current } = result;
expect(current).toHaveLength(1);
expect(current[0].status).toBe('pending');
expect(current[0].statusInconclusive).toBe(false);
expect(current).toHaveLength(1);
expect(current[0].status).toBe('pending');
expect(current[0].statusInconclusive).toBe(false);
});
});
it('Should match Alertmanager url when datasource url does not have protocol specified', async () => {
@ -180,15 +182,16 @@ describe('useExternalDataSourceAlertmanagers', () => {
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
// Act
const { result, waitForValueToChange } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
await waitForValueToChange(() => result.current[0].status);
const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
// Assert
const { current } = result;
await waitFor(() => {
// Assert
const { current } = result;
expect(current).toHaveLength(1);
expect(current[0].status).toBe('active');
expect(current[0].url).toBe('localhost:9093');
expect(current).toHaveLength(1);
expect(current[0].status).toBe('active');
expect(current[0].url).toBe('localhost:9093');
});
});
it('Should have inconclusive state when there are many Alertmanagers of the same URL', async () => {
@ -213,16 +216,16 @@ describe('useExternalDataSourceAlertmanagers', () => {
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
// Act
const { result, waitForValueToChange } = renderHook(() => useExternalDataSourceAlertmanagers(), {
const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), {
wrapper,
});
await waitForValueToChange(() => result.current[0].status);
// Assert
expect(result.current).toHaveLength(1);
expect(result.current[0].status).toBe('active');
expect(result.current[0].statusInconclusive).toBe(true);
await waitFor(() => {
// Assert
expect(result.current).toHaveLength(1);
expect(result.current[0].status).toBe('active');
expect(result.current[0].statusInconclusive).toBe(true);
});
});
});

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';

View File

@ -1,4 +1,4 @@
import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
@ -311,6 +311,7 @@ describe('SharePublic - Already persisted', () => {
describe('SharePublic - Report interactions', () => {
beforeEach(() => {
jest.clearAllMocks();
server.use(getExistentPublicDashboardResponse());
server.use(
rest.put('/api/dashboards/uid/:dashboardUid/public-dashboards/:uid', (req, res, ctx) =>
@ -327,29 +328,41 @@ describe('SharePublic - Report interactions', () => {
it('reports interaction when time range is clicked', async () => {
await renderSharePublicDashboard();
await waitFor(() => {
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
});
await userEvent.click(screen.getByTestId(selectors.EnableTimeRangeSwitch));
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
expect(reportInteraction).toHaveBeenCalledWith('grafana_dashboards_public_time_selection_clicked', {
action: pubdashResponse.timeSelectionEnabled ? 'disable' : 'enable',
await waitFor(() => {
expect(reportInteraction).toHaveBeenCalledWith('grafana_dashboards_public_time_selection_clicked', {
action: pubdashResponse.timeSelectionEnabled ? 'disable' : 'enable',
});
});
});
it('reports interaction when show annotations is clicked', async () => {
await renderSharePublicDashboard();
await waitFor(() => {
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
});
await userEvent.click(screen.getByTestId(selectors.EnableAnnotationsSwitch));
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
expect(reportInteraction).toHaveBeenCalledWith('grafana_dashboards_public_annotations_clicked', {
action: pubdashResponse.annotationsEnabled ? 'disable' : 'enable',
await waitFor(() => {
expect(reportInteraction).toHaveBeenCalledWith('grafana_dashboards_public_annotations_clicked', {
action: pubdashResponse.annotationsEnabled ? 'disable' : 'enable',
});
});
});
it('reports interaction when pause is clicked', async () => {
await renderSharePublicDashboard();
await waitFor(() => {
expect(screen.getByTestId(selectors.PauseSwitch)).toBeEnabled();
});
await userEvent.click(screen.getByTestId(selectors.PauseSwitch));
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
expect(reportInteraction).toHaveBeenCalledWith('grafana_dashboards_public_enable_clicked', {
action: pubdashResponse.isEnabled ? 'disable' : 'enable',
await waitFor(() => {
expect(reportInteraction).toHaveBeenCalledWith('grafana_dashboards_public_enable_clicked', {
action: pubdashResponse.isEnabled ? 'disable' : 'enable',
});
});
});
});

View File

@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { match, Router } from 'react-router-dom';
@ -66,16 +66,6 @@ jest.mock('react-virtualized-auto-sizer', () => {
return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 });
});
interface ScenarioContext {
dashboard?: DashboardModel | null;
container?: HTMLElement;
mount: (propOverrides?: Partial<Props>) => void;
unmount: () => void;
props: Props;
rerender: (propOverrides?: Partial<Props>) => void;
setup: (fn: () => void) => void;
}
function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel {
const data = Object.assign(
{
@ -95,123 +85,98 @@ function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partia
return createDashboardModelFixture(data, metaOverrides);
}
function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioContext) => void) {
describe(description, () => {
let setupFn: () => void;
const mockInitDashboard = jest.fn();
const mockCleanUpDashboardAndVariables = jest.fn();
const ctx: ScenarioContext = {
setup: (fn) => {
setupFn = fn;
},
mount: (propOverrides?: Partial<Props>) => {
config.bootData.navTree = [
{ text: 'Dashboards', id: 'dashboards' },
{ text: 'Home', id: HOME_NAV_ID },
];
function setup(propOverrides?: Partial<Props>) {
config.bootData.navTree = [
{ text: 'Dashboards', id: 'dashboards' },
{ text: 'Home', id: HOME_NAV_ID },
];
const store = configureStore();
const props: Props = {
...getRouteComponentProps({
match: { params: { slug: 'my-dash', uid: '11' } } as unknown as match,
route: { routeName: DashboardRoutes.Normal } as RouteDescriptor,
}),
navIndex: {
dashboards: { text: 'Dashboards', id: 'dashboards', parentItem: { text: 'Home', id: HOME_NAV_ID } },
[HOME_NAV_ID]: { text: 'Home', id: HOME_NAV_ID },
},
initPhase: DashboardInitPhase.NotStarted,
initError: null,
initDashboard: jest.fn(),
notifyApp: mockToolkitActionCreator(notifyApp),
cleanUpDashboardAndVariables: jest.fn(),
cancelVariables: jest.fn(),
templateVarsChangedInUrl: jest.fn(),
dashboard: null,
theme: createTheme(),
};
const store = configureStore();
const props: Props = {
...getRouteComponentProps({
match: { params: { slug: 'my-dash', uid: '11' } } as unknown as match,
route: { routeName: DashboardRoutes.Normal } as RouteDescriptor,
}),
navIndex: {
dashboards: { text: 'Dashboards', id: 'dashboards', parentItem: { text: 'Home', id: HOME_NAV_ID } },
[HOME_NAV_ID]: { text: 'Home', id: HOME_NAV_ID },
},
initPhase: DashboardInitPhase.NotStarted,
initError: null,
initDashboard: mockInitDashboard,
notifyApp: mockToolkitActionCreator(notifyApp),
cleanUpDashboardAndVariables: mockCleanUpDashboardAndVariables,
cancelVariables: jest.fn(),
templateVarsChangedInUrl: jest.fn(),
dashboard: null,
theme: createTheme(),
};
Object.assign(props, propOverrides);
Object.assign(props, propOverrides);
ctx.props = props;
ctx.dashboard = props.dashboard;
const context = getGrafanaContextMock();
const context = getGrafanaContextMock();
const { unmount, rerender } = render(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<UnthemedDashboardPage {...props} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
const { container, rerender, unmount } = render(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<UnthemedDashboardPage {...props} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
const wrappedRerender = (newProps: Partial<Props>) => {
Object.assign(props, newProps);
return rerender(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<UnthemedDashboardPage {...props} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
};
ctx.container = container;
ctx.rerender = (newProps?: Partial<Props>) => {
Object.assign(props, newProps);
rerender(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<UnthemedDashboardPage {...props} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
};
ctx.unmount = unmount;
},
props: {} as Props,
rerender: () => {},
unmount: () => {},
};
beforeEach(() => {
setupFn();
});
scenarioFn(ctx);
});
return { rerender: wrappedRerender, unmount };
}
describe('DashboardPage', () => {
dashboardPageScenario('Given initial state', (ctx) => {
ctx.setup(() => {
ctx.mount();
beforeEach(() => {
jest.clearAllMocks();
});
it('Should call initDashboard on mount', () => {
setup();
expect(mockInitDashboard).toBeCalledWith({
fixUrl: true,
routeName: 'normal-dashboard',
urlSlug: 'my-dash',
urlUid: '11',
keybindingSrv: expect.anything(),
});
});
describe('Given a simple dashboard', () => {
it('Should render panels', async () => {
setup({ dashboard: getTestDashboard() });
expect(await screen.findByText('My panel title')).toBeInTheDocument();
});
it('Should call initDashboard on mount', () => {
expect(ctx.props.initDashboard).toBeCalledWith({
fixUrl: true,
routeName: 'normal-dashboard',
urlSlug: 'my-dash',
urlUid: '11',
keybindingSrv: expect.anything(),
it('Should update title', async () => {
setup({ dashboard: getTestDashboard() });
await waitFor(() => {
expect(document.title).toBe('My dashboard - Dashboards - Grafana');
});
});
});
dashboardPageScenario('Given a simple dashboard', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({ dashboard: getTestDashboard() });
});
it('Should render panels', () => {
expect(screen.getByText('My panel title')).toBeInTheDocument();
});
it('Should update title', () => {
expect(document.title).toBe('My dashboard - Dashboards - Grafana');
});
});
dashboardPageScenario('When going into view mode', (ctx) => {
ctx.setup(() => {
describe('When going into view mode', () => {
beforeEach(() => {
setDataSourceSrv({
get: jest.fn().mockResolvedValue({ getRef: jest.fn(), query: jest.fn().mockResolvedValue([]) }),
getInstanceSettings: jest.fn().mockReturnValue({ meta: {} }),
@ -221,101 +186,110 @@ describe('DashboardPage', () => {
setDashboardSrv({
getCurrent: () => getTestDashboard(),
} as DashboardSrv);
ctx.mount({
dashboard: getTestDashboard(),
});
it('Should render panel in view mode', async () => {
const dashboard = getTestDashboard();
setup({
dashboard,
queryParams: { viewPanel: '1' },
});
await waitFor(() => {
expect(dashboard.panelInView).toBeDefined();
expect(dashboard.panels[0].isViewing).toBe(true);
});
});
it('Should render panel in view mode', () => {
expect(ctx.dashboard?.panelInView).toBeDefined();
expect(ctx.dashboard?.panels[0].isViewing).toBe(true);
});
it('Should reset state when leaving', async () => {
const dashboard = getTestDashboard();
const { rerender } = setup({
dashboard,
queryParams: { viewPanel: '1' },
});
rerender({ queryParams: {}, dashboard });
it('Should reset state when leaving', () => {
ctx.rerender({ queryParams: {} });
expect(ctx.dashboard?.panelInView).toBeUndefined();
expect(ctx.dashboard?.panels[0].isViewing).toBe(false);
await waitFor(() => {
expect(dashboard.panelInView).toBeUndefined();
expect(dashboard.panels[0].isViewing).toBe(false);
});
});
});
dashboardPageScenario('When going into edit mode', (ctx) => {
ctx.setup(() => {
ctx.mount({
dashboard: getTestDashboard(),
describe('When going into edit mode', () => {
it('Should render panel in edit mode', async () => {
const dashboard = getTestDashboard();
setup({
dashboard,
queryParams: { editPanel: '1' },
});
await waitFor(() => {
expect(dashboard.panelInEdit).toBeDefined();
});
});
it('Should render panel in edit mode', () => {
expect(ctx.dashboard?.panelInEdit).toBeDefined();
it('Should render panel editor', async () => {
const dashboard = getTestDashboard();
setup({
dashboard,
queryParams: { editPanel: '1' },
});
expect(await screen.findByTitle('Apply changes and go back to dashboard')).toBeInTheDocument();
});
it('Should render panel editor', () => {
expect(screen.getByTitle('Apply changes and go back to dashboard')).toBeInTheDocument();
});
it('Should reset state when leaving', () => {
ctx.rerender({ queryParams: {} });
expect(screen.queryByTitle('Apply changes and go back to dashboard')).not.toBeInTheDocument();
it('Should reset state when leaving', async () => {
const dashboard = getTestDashboard();
const { rerender } = setup({
dashboard,
queryParams: { editPanel: '1' },
});
rerender({ queryParams: {} });
await waitFor(() => {
expect(screen.queryByTitle('Apply changes and go back to dashboard')).not.toBeInTheDocument();
});
});
});
dashboardPageScenario('When dashboard unmounts', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({ dashboard: getTestDashboard() });
ctx.unmount();
});
it('Should call close action', () => {
expect(ctx.props.cleanUpDashboardAndVariables).toHaveBeenCalledTimes(1);
describe('When dashboard unmounts', () => {
it('Should call close action', async () => {
const { rerender, unmount } = setup();
rerender({ dashboard: getTestDashboard() });
unmount();
await waitFor(() => {
expect(mockCleanUpDashboardAndVariables).toHaveBeenCalledTimes(1);
});
});
});
dashboardPageScenario('When dashboard changes', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({ dashboard: getTestDashboard() });
ctx.rerender({
match: {
params: { uid: 'new-uid' },
} as unknown as match,
describe('When dashboard changes', () => {
it('Should call clean up action and init', async () => {
const { rerender } = setup();
rerender({ dashboard: getTestDashboard() });
rerender({
match: { params: { uid: 'new-uid' } } as unknown as match,
dashboard: getTestDashboard({ title: 'Another dashboard' }),
});
});
it('Should call clean up action and init', () => {
expect(ctx.props.cleanUpDashboardAndVariables).toHaveBeenCalledTimes(1);
expect(ctx.props.initDashboard).toHaveBeenCalledTimes(2);
});
});
dashboardPageScenario('No kiosk mode tv', (ctx) => {
ctx.setup(() => {
ctx.mount({ dashboard: getTestDashboard() });
ctx.rerender({ dashboard: ctx.dashboard });
});
it('should render dashboard page toolbar and submenu', () => {
expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(1);
expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(1);
});
});
dashboardPageScenario('When in full kiosk mode', (ctx) => {
ctx.setup(() => {
ctx.mount({
queryParams: { kiosk: true },
dashboard: getTestDashboard(),
await waitFor(() => {
expect(mockCleanUpDashboardAndVariables).toHaveBeenCalledTimes(1);
expect(mockInitDashboard).toHaveBeenCalledTimes(2);
});
ctx.rerender({ dashboard: ctx.dashboard });
});
});
it('should not render page toolbar and submenu', () => {
expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(0);
expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0);
describe('No kiosk mode tv', () => {
it('should render dashboard page toolbar and submenu', async () => {
setup({ dashboard: getTestDashboard() });
expect(await screen.findAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(1);
expect(screen.getAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(1);
});
});
describe('When in full kiosk mode', () => {
it('should not render page toolbar and submenu', async () => {
setup({ dashboard: getTestDashboard(), queryParams: { kiosk: true } });
await waitFor(() => {
expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(0);
expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0);
});
});
});
});

View File

@ -1,4 +1,4 @@
import { render, RenderResult, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
@ -48,29 +48,27 @@ jest.mock('app/types', () => ({
useDispatch: () => jest.fn(),
}));
interface ScenarioContext {
mount: () => void;
rerender: ({
propOverrides,
newState,
}: {
propOverrides?: Partial<Props>;
newState?: Partial<appTypes.StoreState>;
}) => void;
setup: (fn: () => void) => void;
}
const renderWithProvider = ({
props,
initialState,
}: {
props: Props;
initialState?: Partial<appTypes.StoreState>;
}): RenderResult => {
const setup = (propOverrides?: Partial<Props>, initialState?: Partial<appTypes.StoreState>) => {
const context = getGrafanaContextMock();
const store = configureStore(initialState);
return render(
const props: Props = {
...getRouteComponentProps({
match: { params: { accessToken: 'an-access-token' }, isExact: true, url: '', path: '' },
route: {
routeName: DashboardRoutes.Public,
path: '/public-dashboards/:accessToken',
component: SafeDynamicImport(
() =>
import(/* webpackChunkName: "PublicDashboardPage"*/ 'app/features/dashboard/containers/PublicDashboardPage')
),
},
}),
};
Object.assign(props, propOverrides);
const { unmount, rerender } = render(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
@ -79,6 +77,21 @@ const renderWithProvider = ({
</Provider>
</GrafanaContext.Provider>
);
const wrappedRerender = (newProps: Partial<Props>) => {
Object.assign(props, newProps);
return rerender(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<PublicDashboardPage {...props} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
};
return { rerender: wrappedRerender, unmount };
};
const selectors = e2eSelectors.components;
@ -110,185 +123,114 @@ const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partia
return new DashboardModel(data, metaOverrides);
};
function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioContext) => void) {
describe(description, () => {
let setupFn: () => void;
describe('PublicDashboardPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const ctx: ScenarioContext = {
setup: (fn) => {
setupFn = fn;
it('Should call initDashboard on mount', () => {
setup();
expect(initDashboard).toBeCalledWith({
fixUrl: false,
accessToken: 'an-access-token',
routeName: 'public-dashboard',
keybindingSrv: expect.anything(),
});
});
describe('Given a simple public dashboard', () => {
const newState = {
dashboard: {
getModel: getTestDashboard,
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
mount: () => {
const props: Props = {
...getRouteComponentProps({
match: { params: { accessToken: 'an-access-token' }, isExact: true, url: '', path: '' },
route: {
routeName: DashboardRoutes.Public,
path: '/public-dashboards/:accessToken',
component: SafeDynamicImport(
() =>
import(
/* webpackChunkName: "PublicDashboardPage"*/ 'app/features/dashboard/containers/PublicDashboardPage'
)
),
},
}),
};
const { rerender } = renderWithProvider({ props });
ctx.rerender = ({
propsOverride,
newState,
}: {
propsOverride?: Partial<Props>;
newState?: Partial<appTypes.StoreState>;
}) => {
Object.assign(props, propsOverride);
const context = getGrafanaContextMock();
const store = configureStore(newState);
rerender(
<GrafanaContext.Provider value={context}>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<PublicDashboardPage {...props} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
};
},
rerender: () => {},
};
beforeEach(() => {
setupFn();
it('Should render panels', async () => {
setup(undefined, newState);
expect(await screen.findByText('My panel title')).toBeInTheDocument();
});
scenarioFn(ctx);
});
}
describe('PublicDashboardPage', () => {
dashboardPageScenario('Given initial state', (ctx) => {
ctx.setup(() => {
ctx.mount();
it('Should update title', async () => {
setup(undefined, newState);
await waitFor(() => {
expect(document.title).toBe('My dashboard - Grafana');
});
});
it('Should call initDashboard on mount', () => {
expect(initDashboard).toBeCalledWith({
fixUrl: false,
accessToken: 'an-access-token',
routeName: 'public-dashboard',
keybindingSrv: expect.anything(),
it('Should not render neither time range nor refresh picker buttons', async () => {
setup(undefined, newState);
await waitFor(() => {
expect(screen.queryByTestId(selectors.TimePicker.openButton)).not.toBeInTheDocument();
expect(screen.queryByTestId(selectors.RefreshPicker.runButtonV2)).not.toBeInTheDocument();
expect(screen.queryByTestId(selectors.RefreshPicker.intervalButtonV2)).not.toBeInTheDocument();
});
});
it('Should not render paused or deleted screen', async () => {
setup(undefined, newState);
await waitFor(() => {
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.container)).not.toBeInTheDocument();
});
});
});
dashboardPageScenario('Given a simple public dashboard', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({
newState: {
dashboard: {
getModel: getTestDashboard,
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
describe('Given a public dashboard with time range enabled', () => {
it('Should render time range and refresh picker buttons', async () => {
setup(undefined, {
dashboard: {
getModel: () =>
getTestDashboard({
timepicker: { hidden: false, collapse: false, enable: true, refresh_intervals: [], time_options: [] },
}),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
});
});
it('Should render panels', () => {
expect(screen.getByText('My panel title')).toBeInTheDocument();
});
it('Should update title', () => {
expect(document.title).toBe('My dashboard - Grafana');
});
it('Should not render neither time range nor refresh picker buttons', () => {
expect(screen.queryByTestId(selectors.TimePicker.openButton)).not.toBeInTheDocument();
expect(screen.queryByTestId(selectors.RefreshPicker.runButtonV2)).not.toBeInTheDocument();
expect(screen.queryByTestId(selectors.RefreshPicker.intervalButtonV2)).not.toBeInTheDocument();
});
it('Should not render paused or deleted screen', () => {
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.container)).not.toBeInTheDocument();
});
});
dashboardPageScenario('Given a public dashboard with time range enabled', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({
newState: {
dashboard: {
getModel: () =>
getTestDashboard({
timepicker: { hidden: false, collapse: false, enable: true, refresh_intervals: [], time_options: [] },
}),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
},
});
});
it('Should render time range and refresh picker buttons', () => {
expect(screen.getByTestId(selectors.TimePicker.openButton)).toBeInTheDocument();
expect(await screen.findByTestId(selectors.TimePicker.openButton)).toBeInTheDocument();
expect(screen.getByTestId(selectors.RefreshPicker.runButtonV2)).toBeInTheDocument();
expect(screen.getByTestId(selectors.RefreshPicker.intervalButtonV2)).toBeInTheDocument();
});
});
dashboardPageScenario('Given paused public dashboard', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({
newState: {
dashboard: {
getModel: () => getTestDashboard(undefined, { publicDashboardEnabled: false, dashboardNotFound: false }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
describe('Given paused public dashboard', () => {
it('Should render public dashboard paused screen', async () => {
setup(undefined, {
dashboard: {
getModel: () => getTestDashboard(undefined, { publicDashboardEnabled: false, dashboardNotFound: false }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
});
});
it('Should render public dashboard paused screen', () => {
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
});
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).toBeInTheDocument();
});
});
dashboardPageScenario('Given deleted public dashboard', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({
newState: {
dashboard: {
getModel: () => getTestDashboard(undefined, { dashboardNotFound: true }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
describe('Given deleted public dashboard', () => {
it('Should render public dashboard deleted screen', async () => {
setup(undefined, {
dashboard: {
getModel: () => getTestDashboard(undefined, { dashboardNotFound: true }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
});
});
it('Should render public dashboard deleted screen', () => {
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
});
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
});
});
});

View File

@ -8,9 +8,10 @@ import { DashboardModel } from '../state';
import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures';
import { DashboardGrid, Props } from './DashboardGrid';
import { Props as LazyLoaderProps } from './LazyLoader';
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
const LazyLoader = ({ children }: React.PropsWithChildren<{}>) => {
const LazyLoader = ({ children }: LazyLoaderProps) => {
return <>{children}</>;
};
return { LazyLoader };

View File

@ -71,7 +71,7 @@ export class DashboardPanelUnconnected extends PureComponent<Props> {
}
};
renderPanel = (isInView: boolean) => {
renderPanel = ({ isInView }: { isInView: boolean }) => {
const { dashboard, panel, isViewing, isEditing, width, height, plugin, timezone, hideMenu } = this.props;
if (!plugin) {
@ -118,7 +118,7 @@ export class DashboardPanelUnconnected extends PureComponent<Props> {
{this.renderPanel}
</LazyLoader>
) : (
this.renderPanel(true)
this.renderPanel({ isInView: true })
);
}
}

View File

@ -55,7 +55,9 @@ LazyLoader.addCallback = (id: string, c: (e: IntersectionObserverEntry) => void)
LazyLoader.observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
LazyLoader.callbacks[entry.target.id](entry);
if (LazyLoader.callbacks[entry.target.id]) {
LazyLoader.callbacks[entry.target.id](entry);
}
}
},
{ rootMargin: '100px' }

View File

@ -26,7 +26,7 @@ export function DataSourceTestingStatus({ testingStatus }: Props) {
<>
{detailsMessage}
{detailsVerboseMessage ? (
<details style={{ whiteSpace: 'pre-wrap' }}>{detailsVerboseMessage}</details>
<details style={{ whiteSpace: 'pre-wrap' }}>{String(detailsVerboseMessage)}</details>
) : null}
</>
)}

View File

@ -110,9 +110,9 @@ class ExplorePaneContainerUnconnected extends React.PureComponent<Props> {
rootDatasourceOverride = changeDatasourceUid;
const datasource = await getDatasourceSrv().get(changeDatasourceUid);
const datasourceInit = await getDatasourceSrv().get(initialDatasource);
await this.props.importQueries(exploreId, queries, datasourceInit, datasource);
const newQueries = await this.props.importQueries(exploreId, queries, datasourceInit, datasource);
await this.props.stateSave({ replace: true });
queries = this.props.initialQueries;
queries = newQueries ?? this.props.initialQueries;
}
}
}

View File

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import { TraceSpan } from './components';
import { useChildrenState } from './useChildrenState';

View File

@ -1,4 +1,4 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react';
import { DataFrame } from '@grafana/data';

View File

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import { useHoverIndentGuide } from './useHoverIndentGuide';

View File

@ -1,4 +1,4 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react';
import { TraceSpan } from './components';
import { useSearch } from './useSearch';

View File

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import { useViewRange } from './useViewRange';

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import { fromPairs } from 'lodash';
import React from 'react';
import { Provider } from 'react-redux';

View File

@ -339,7 +339,7 @@ export const importQueries = (
sourceDataSource: DataSourceApi | undefined | null,
targetDataSource: DataSourceApi,
singleQueryChangeRef?: string // when changing one query DS to another in a mixed environment, we do not want to change all queries, just the one being changed
): ThunkResult<Promise<void>> => {
): ThunkResult<Promise<DataQuery[] | void>> => {
return async (dispatch) => {
if (!sourceDataSource) {
// explore not initialized
@ -396,6 +396,7 @@ export const importQueries = (
}
dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
return nextQueries;
};
};

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

View File

@ -357,7 +357,11 @@ export const LogRowContextGroup = ({
padding: 5px 0;
`}
>
{typeof item === 'string' && textUtil.hasAnsiCodes(item) ? <LogMessageAnsi value={item} /> : item}
{typeof item === 'string' && textUtil.hasAnsiCodes(item) ? (
<LogMessageAnsi value={item} />
) : (
String(item)
)}
</div>
);
}}

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

View File

@ -1,5 +1,4 @@
import { within } from '@testing-library/dom';
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
import React from 'react';

View File

@ -187,7 +187,7 @@ export const generateColumns = (
},
id: `column-location`,
field: access.location ?? access.url,
Header: () => t('search.results-table.location-header', 'Location'),
Header: t('search.results-table.location-header', 'Location'),
width,
});
}
@ -274,7 +274,7 @@ function makeDataSourceColumn(
return {
id: `column-datasource`,
field,
Header: () => t('search.results-table.datasource-header', 'Data source'),
Header: t('search.results-table.datasource-header', 'Data source'),
Cell: (p) => {
const dslist = field.values.get(p.row.index);
if (!dslist?.length) {
@ -322,7 +322,7 @@ function makeTypeColumn(
return {
id: `column-type`,
field: kindField ?? typeField,
Header: () => t('search.results-table.type-header', 'Type'),
Header: t('search.results-table.type-header', 'Type'),
Cell: (p) => {
const i = p.row.index;
const kind = kindField?.values.get(i) ?? 'dashboard';
@ -393,7 +393,7 @@ function makeTagsColumn(
},
id: `column-tags`,
field: field,
Header: () => t('search.results-table.tags-header', 'Tags'),
Header: t('search.results-table.tags-header', 'Tags'),
width,
};
}

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, waitFor } from '@testing-library/react';
import createMockDatasource from '../../__mocks__/datasource';
import Datasource from '../../datasource';
@ -13,10 +13,6 @@ import {
MetricsMetadataHook,
} from './dataHooks';
const WAIT_OPTIONS = {
timeout: 1000,
};
const opt = (text: string, value: string) => ({ text, value });
interface TestScenario {
@ -180,10 +176,11 @@ describe('AzureMonitor: metrics dataHooks', () => {
...bareQuery,
azureMonitor: scenario.emptyQueryPartial,
};
const { result, waitForNextUpdate } = renderHook(() => scenario.hook(query, datasource, onChange, setError));
await waitForNextUpdate(WAIT_OPTIONS);
const { result } = renderHook(() => scenario.hook(query, datasource, onChange, setError));
expect(result.current).toEqual(scenario.expectedOptions);
await waitFor(() => {
expect(result.current).toEqual(scenario.expectedOptions);
});
});
it('adds custom properties as a valid option', async () => {
@ -192,10 +189,11 @@ describe('AzureMonitor: metrics dataHooks', () => {
azureMonitor: scenario.customProperties,
...scenario.topLevelCustomProperties,
};
const { result, waitForNextUpdate } = renderHook(() => scenario.hook(query, datasource, onChange, setError));
await waitForNextUpdate(WAIT_OPTIONS);
const { result } = renderHook(() => scenario.hook(query, datasource, onChange, setError));
expect(result.current).toEqual(scenario.expectedCustomPropertyResults);
await waitFor(() => {
expect(result.current).toEqual(scenario.expectedCustomPropertyResults);
});
});
});
@ -239,18 +237,19 @@ describe('AzureMonitor: metrics dataHooks', () => {
...bareQuery,
azureMonitor: metricsMetadataConfig.emptyQueryPartial,
};
const { result, waitForNextUpdate } = renderHook(() => metricsMetadataConfig.hook(query, datasource, onChange));
await waitForNextUpdate(WAIT_OPTIONS);
const { result } = renderHook(() => metricsMetadataConfig.hook(query, datasource, onChange));
expect(result.current).toEqual(metricsMetadataConfig.expectedOptions);
expect(onChange).toHaveBeenCalledWith({
...query,
azureMonitor: {
...query.azureMonitor,
aggregation: result.current.primaryAggType,
timeGrain: 'auto',
allowedTimeGrainsMs: [60_000, 300_000, 900_000, 1_800_000, 3_600_000, 21_600_000, 43_200_000, 86_400_000],
},
await waitFor(() => {
expect(result.current).toEqual(metricsMetadataConfig.expectedOptions);
expect(onChange).toHaveBeenCalledWith({
...query,
azureMonitor: {
...query.azureMonitor,
aggregation: result.current.primaryAggType,
timeGrain: 'auto',
allowedTimeGrainsMs: [60_000, 300_000, 900_000, 1_800_000, 3_600_000, 21_600_000, 43_200_000, 86_400_000],
},
});
});
});
});
@ -281,21 +280,20 @@ describe('AzureMonitor: metrics dataHooks', () => {
...bareQuery,
azureMonitor: metricNamespacesConfig.emptyQueryPartial,
};
const { result, waitForNextUpdate } = renderHook(() =>
metricNamespacesConfig.hook(query, datasource, onChange, jest.fn())
);
await waitForNextUpdate(WAIT_OPTIONS);
const { result } = renderHook(() => metricNamespacesConfig.hook(query, datasource, onChange, jest.fn()));
expect(result.current).toEqual(metricNamespacesConfig.expectedOptions);
expect(datasource.azureMonitorDatasource.getMetricNamespaces).toHaveBeenCalledWith(
expect.objectContaining({
resourceGroup: 'rg',
resourceName: 'rn',
metricNamespace: 'azure/vm',
}),
// Here, "global" should be false
false
);
await waitFor(() => {
expect(result.current).toEqual(metricNamespacesConfig.expectedOptions);
expect(datasource.azureMonitorDatasource.getMetricNamespaces).toHaveBeenCalledWith(
expect.objectContaining({
resourceGroup: 'rg',
resourceName: 'rn',
metricNamespace: 'azure/vm',
}),
// Here, "global" should be false
false
);
});
});
});
});

View File

@ -71,7 +71,7 @@ const QueryEditor = ({
<>
<Space v={2} />
<Alert severity="error" title="An error occurred while requesting metadata from Azure Monitor">
{errorMessage}
{errorMessage instanceof Error ? errorMessage.message : errorMessage}
</Alert>
</>
)}

View File

@ -89,20 +89,20 @@ describe('AzureMonitor ResourcePicker', () => {
it('should show a subscription as selected if there is one saved', async () => {
render(<ResourcePicker {...defaultProps} resources={[singleSubscriptionSelectionURI]} />);
await waitFor(async () => {
const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription');
expect(subscriptionCheckboxes.length).toBe(2);
await waitFor(() => {
expect(screen.getAllByLabelText('Dev Subscription')).toHaveLength(2);
});
const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription');
expect(subscriptionCheckboxes.length).toBe(2);
expect(subscriptionCheckboxes[0]).toBeChecked();
expect(subscriptionCheckboxes[1]).toBeChecked();
});
it('should show a resourceGroup as selected if there is one saved', async () => {
render(<ResourcePicker {...defaultProps} resources={[singleResourceGroupSelectionURI]} />);
await waitFor(() => {
expect(screen.getAllByLabelText('A Great Resource Group')).toHaveLength(2);
});
const resourceGroupCheckboxes = await screen.findAllByLabelText('A Great Resource Group');
expect(resourceGroupCheckboxes.length).toBe(2);
expect(resourceGroupCheckboxes[0]).toBeChecked();
expect(resourceGroupCheckboxes[1]).toBeChecked();
});
@ -112,8 +112,10 @@ describe('AzureMonitor ResourcePicker', () => {
await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getAllByLabelText('db-server')).toHaveLength(2);
});
const resourceCheckboxes = await screen.findAllByLabelText('db-server');
expect(resourceCheckboxes.length).toBe(2);
expect(resourceCheckboxes[0]).toBeChecked();
expect(resourceCheckboxes[1]).toBeChecked();
});
@ -158,8 +160,10 @@ describe('AzureMonitor ResourcePicker', () => {
it('should call onApply removing an element', async () => {
const onApply = jest.fn();
render(<ResourcePicker {...defaultProps} resources={['/subscriptions/def-123']} onApply={onApply} />);
await waitFor(() => {
expect(screen.getAllByLabelText('Primary Subscription')).toHaveLength(2);
});
const subscriptionCheckbox = await screen.findAllByLabelText('Primary Subscription');
expect(subscriptionCheckbox).toHaveLength(2);
expect(subscriptionCheckbox.at(0)).toBeChecked();
await userEvent.click(subscriptionCheckbox.at(0)!);
const applyButton = screen.getByRole('button', { name: 'Apply' });
@ -173,8 +177,10 @@ describe('AzureMonitor ResourcePicker', () => {
render(
<ResourcePicker {...defaultProps} resources={['/subscriptions/def-456/resourceGroups/DEV-3']} onApply={onApply} />
);
await waitFor(() => {
expect(screen.getAllByLabelText('A Great Resource Group')).toHaveLength(2);
});
const subscriptionCheckbox = await screen.findAllByLabelText('A Great Resource Group');
expect(subscriptionCheckbox).toHaveLength(2);
expect(subscriptionCheckbox.at(0)).toBeChecked();
await userEvent.click(subscriptionCheckbox.at(0)!);
const applyButton = screen.getByRole('button', { name: 'Apply' });
@ -232,8 +238,10 @@ describe('AzureMonitor ResourcePicker', () => {
await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getAllByLabelText('web-server')).toHaveLength(2);
});
const checkbox = await screen.findAllByLabelText('web-server');
expect(checkbox).toHaveLength(2);
expect(checkbox.at(0)).toBeChecked();
await userEvent.click(checkbox.at(0)!);
const applyButton = screen.getByRole('button', { name: 'Apply' });

View File

@ -256,7 +256,7 @@ const VariableEditor = (props: Props) => {
<>
<Space v={2} />
<Alert severity="error" title="An error occurred while requesting metadata from Azure Monitor">
{errorMessage}
{errorMessage instanceof Error ? errorMessage.message : errorMessage}
</Alert>
</>
)}
@ -365,7 +365,7 @@ const VariableEditor = (props: Props) => {
<>
<Space v={2} />
<Alert severity="error" title="An error occurred while requesting metadata from Azure Monitor">
{errorMessage}
{errorMessage instanceof Error ? errorMessage.message : errorMessage}
</Alert>
</>
)}

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, waitFor } from '@testing-library/react';
import { useAsyncState } from './useAsyncState';
@ -36,10 +36,11 @@ describe('useAsyncState', () => {
const apiCall = () => Promise.resolve(['a', 'b', 'c']);
const setError = jest.fn();
const { result, waitForNextUpdate } = renderHook(() => useAsyncState(apiCall, setError, []));
await waitForNextUpdate();
const { result } = renderHook(() => useAsyncState(apiCall, setError, []));
expect(result.current).toEqual(['a', 'b', 'c']);
await waitFor(() => {
expect(result.current).toEqual(['a', 'b', 'c']);
});
});
it('should report errors through setError', async () => {
@ -47,20 +48,22 @@ describe('useAsyncState', () => {
const apiCall = () => Promise.reject(error);
const setError = createWaitableMock();
const { result, waitForNextUpdate } = renderHook(() => useAsyncState(apiCall, setError, []));
await Promise.race([waitForNextUpdate(), setError.waitToBeCalled()]);
const { result } = renderHook(() => useAsyncState(apiCall, setError, []));
expect(result.current).toEqual([]);
expect(setError).toHaveBeenCalledWith(MOCKED_RANDOM_VALUE, error);
await waitFor(() => {
expect(result.current).toEqual([]);
expect(setError).toHaveBeenCalledWith(MOCKED_RANDOM_VALUE, error);
});
});
it('should clear the error once the request is successful', async () => {
const apiCall = () => Promise.resolve(['a', 'b', 'c']);
const setError = createWaitableMock();
const { waitForNextUpdate } = renderHook(() => useAsyncState(apiCall, setError, []));
await Promise.race([waitForNextUpdate(), setError.waitToBeCalled()]);
renderHook(() => useAsyncState(apiCall, setError, []));
expect(setError).toHaveBeenCalledWith(MOCKED_RANDOM_VALUE, undefined);
await waitFor(() => {
expect(setError).toHaveBeenCalledWith(MOCKED_RANDOM_VALUE, undefined);
});
});
});

View File

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import useLastError from './useLastError';

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, waitFor } from '@testing-library/react';
import { config } from '@grafana/runtime';
@ -13,10 +13,6 @@ import {
import { setupMockedResourcesAPI } from './__mocks__/ResourcesAPI';
import { useAccountOptions, useDimensionKeys, useIsMonitoringAccount, useMetrics } from './hooks';
const WAIT_OPTIONS = {
timeout: 1000,
};
const originalFeatureToggleValue = config.featureToggles.cloudWatchCrossAccountQuerying;
describe('hooks', () => {
@ -32,10 +28,11 @@ describe('hooks', () => {
const isMonitoringAccountMock = jest.fn().mockResolvedValue(true);
api.isMonitoringAccount = isMonitoringAccountMock;
const { waitForNextUpdate } = renderHook(() => useIsMonitoringAccount(api, `$${regionVariable.name}`));
await waitForNextUpdate(WAIT_OPTIONS);
expect(isMonitoringAccountMock).toHaveBeenCalledTimes(1);
expect(isMonitoringAccountMock).toHaveBeenCalledWith(regionVariable.current.value);
renderHook(() => useIsMonitoringAccount(api, `$${regionVariable.name}`));
await waitFor(() => {
expect(isMonitoringAccountMock).toHaveBeenCalledTimes(1);
expect(isMonitoringAccountMock).toHaveBeenCalledWith(regionVariable.current.value);
});
});
});
describe('useMetricNames', () => {
@ -46,19 +43,20 @@ describe('hooks', () => {
const getMetricsMock = jest.fn().mockResolvedValue([]);
datasource.resources.getMetrics = getMetricsMock;
const { waitForNextUpdate } = renderHook(() =>
renderHook(() =>
useMetrics(datasource, {
namespace: `$${namespaceVariable.name}`,
region: `$${regionVariable.name}`,
accountId: `$${accountIdVariable.name}`,
})
);
await waitForNextUpdate(WAIT_OPTIONS);
expect(getMetricsMock).toHaveBeenCalledTimes(1);
expect(getMetricsMock).toHaveBeenCalledWith({
region: regionVariable.current.value,
namespace: namespaceVariable.current.value,
accountId: accountIdVariable.current.value,
await waitFor(() => {
expect(getMetricsMock).toHaveBeenCalledTimes(1);
expect(getMetricsMock).toHaveBeenCalledWith({
region: regionVariable.current.value,
namespace: namespaceVariable.current.value,
accountId: accountIdVariable.current.value,
});
});
});
});
@ -72,7 +70,7 @@ describe('hooks', () => {
const getDimensionKeysMock = jest.fn().mockResolvedValue([]);
datasource.resources.getDimensionKeys = getDimensionKeysMock;
const { waitForNextUpdate } = renderHook(() =>
renderHook(() =>
useDimensionKeys(datasource, {
namespace: `$${namespaceVariable.name}`,
metricName: `$${metricVariable.name}`,
@ -83,16 +81,17 @@ describe('hooks', () => {
},
})
);
await waitForNextUpdate(WAIT_OPTIONS);
expect(getDimensionKeysMock).toHaveBeenCalledTimes(1);
expect(getDimensionKeysMock).toHaveBeenCalledWith({
region: regionVariable.current.value,
namespace: namespaceVariable.current.value,
metricName: metricVariable.current.value,
accountId: accountIdVariable.current.value,
dimensionFilters: {
environment: [dimensionVariable.current.value],
},
await waitFor(() => {
expect(getDimensionKeysMock).toHaveBeenCalledTimes(1);
expect(getDimensionKeysMock).toHaveBeenCalledWith({
region: regionVariable.current.value,
namespace: namespaceVariable.current.value,
metricName: metricVariable.current.value,
accountId: accountIdVariable.current.value,
dimensionFilters: {
environment: [dimensionVariable.current.value],
},
});
});
});
});
@ -105,9 +104,10 @@ describe('hooks', () => {
});
const getAccountsMock = jest.fn().mockResolvedValue([{ id: '123', label: 'accountLabel' }]);
api.getAccounts = getAccountsMock;
const { waitForNextUpdate } = renderHook(() => useAccountOptions(api, `$${regionVariable.name}`));
await waitForNextUpdate(WAIT_OPTIONS);
expect(getAccountsMock).toHaveBeenCalledTimes(0);
renderHook(() => useAccountOptions(api, `$${regionVariable.name}`));
await waitFor(() => {
expect(getAccountsMock).toHaveBeenCalledTimes(0);
});
});
it('interpolates region variables before calling the api', async () => {
@ -117,10 +117,11 @@ describe('hooks', () => {
});
const getAccountsMock = jest.fn().mockResolvedValue([{ id: '123', label: 'accountLabel' }]);
api.getAccounts = getAccountsMock;
const { waitForNextUpdate } = renderHook(() => useAccountOptions(api, `$${regionVariable.name}`));
await waitForNextUpdate(WAIT_OPTIONS);
expect(getAccountsMock).toHaveBeenCalledTimes(1);
expect(getAccountsMock).toHaveBeenCalledWith({ region: regionVariable.current.value });
renderHook(() => useAccountOptions(api, `$${regionVariable.name}`));
await waitFor(() => {
expect(getAccountsMock).toHaveBeenCalledTimes(1);
expect(getAccountsMock).toHaveBeenCalledWith({ region: regionVariable.current.value });
});
});
it('returns properly formatted account options, and template variables', async () => {
@ -130,12 +131,13 @@ describe('hooks', () => {
});
const getAccountsMock = jest.fn().mockResolvedValue([{ id: '123', label: 'accountLabel' }]);
api.getAccounts = getAccountsMock;
const { waitForNextUpdate, result } = renderHook(() => useAccountOptions(api, `$${regionVariable.name}`));
await waitForNextUpdate(WAIT_OPTIONS);
expect(result.current.value).toEqual([
{ label: 'accountLabel', description: '123', value: '123' },
{ label: 'Template Variables', options: [{ label: '$region', value: '$region' }] },
]);
const { result } = renderHook(() => useAccountOptions(api, `$${regionVariable.name}`));
await waitFor(() => {
expect(result.current.value).toEqual([
{ label: 'accountLabel', description: '123', value: '123' },
{ label: 'Template Variables', options: [{ label: '$region', value: '$region' }] },
]);
});
});
});
});

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import { config } from '@grafana/runtime';

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import React, { PropsWithChildren } from 'react';
import { from } from 'rxjs';

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import React, { PropsWithChildren } from 'react';
import { getDefaultTimeRange } from '@grafana/data';

View File

@ -1,4 +1,4 @@
import { act, render } from '@testing-library/react';
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import InfluxDatasource from '../../datasource';
@ -41,10 +41,10 @@ async function assertEditor(query: InfluxQuery, textContent: string) {
const datasource: InfluxDatasource = {
metricFindQuery: () => Promise.resolve([]),
} as unknown as InfluxDatasource;
await act(async () => {
const { container } = await render(
<Editor query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
);
const { container } = render(
<Editor query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
);
await waitFor(() => {
expect(container.textContent).toBe(textContent);
});
}

View File

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import { useShadowedState } from './useShadowedState';

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import { useUniqueId } from './useUniqueId';

View File

@ -118,14 +118,13 @@ describe('LokiContextUi', () => {
render(<LokiContextUi {...props} />);
await waitFor(() => {
expect(props.languageProvider.start).toHaveBeenCalled();
expect(screen.getAllByRole('combobox')).toHaveLength(2);
});
const select = await screen.findAllByRole('combobox');
await selectOptionInTest(select[1], 'label3');
await selectOptionInTest(screen.getAllByRole('combobox')[1], 'label3');
act(() => {
jest.runAllTimers();
});
expect(props.updateFilter).toHaveBeenCalled();
jest.useRealTimers();
});

View File

@ -1,5 +1,4 @@
import { screen } from '@testing-library/dom';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

View File

@ -1,5 +1,4 @@
import { screen } from '@testing-library/dom';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

View File

@ -1,4 +1,4 @@
import { getByTestId, render, screen } from '@testing-library/react';
import { getByTestId, render, screen, waitFor } from '@testing-library/react';
// @ts-ignore
import userEvent from '@testing-library/user-event';
import React from 'react';
@ -141,11 +141,13 @@ describe('PromQueryField', () => {
// If we check the label browser right away it should be in loading state
let labelBrowser = screen.getByRole('button');
expect(labelBrowser.textContent).toContain('Loading');
expect(labelBrowser).toHaveTextContent('Loading');
// wait for component to rerender
labelBrowser = await screen.findByRole('button');
expect(labelBrowser.textContent).toContain('Metrics browser');
await waitFor(() => {
expect(labelBrowser).toHaveTextContent('Metrics browser');
});
});
it('should not run query onBlur', async () => {

View File

@ -1,4 +1,4 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react';
import { lokiQueryEditorExplainKey, promQueryEditorExplainKey, useFlag } from './useFlag';

View File

@ -1,5 +1,4 @@
import { render, screen } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react-hooks';
import { act, render, renderHook, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { CascaderOption } from '@grafana/ui';
@ -38,12 +37,13 @@ describe('useServices', () => {
},
} as ZipkinDatasource;
const { result, waitForNextUpdate } = renderHook(() => useServices(ds));
await waitForNextUpdate();
expect(result.current.value).toEqual([
{ label: 'service1', value: 'service1', isLeaf: false },
{ label: 'service2', value: 'service2', isLeaf: false },
]);
const { result } = renderHook(() => useServices(ds));
await waitFor(() => {
expect(result.current.value).toEqual([
{ label: 'service1', value: 'service1', isLeaf: false },
{ label: 'service2', value: 'service2', isLeaf: false },
]);
});
});
});
@ -62,25 +62,25 @@ describe('useLoadOptions', () => {
},
} as ZipkinDatasource;
const { result, waitForNextUpdate } = renderHook(() => useLoadOptions(ds));
const { result } = renderHook(() => useLoadOptions(ds));
expect(result.current.allOptions).toEqual({});
act(() => {
result.current.onLoadOptions([{ value: 'service1' } as CascaderOption]);
});
await waitForNextUpdate();
expect(result.current.allOptions).toEqual({ service1: { span1: undefined, span2: undefined } });
await waitFor(() => {
expect(result.current.allOptions).toEqual({ service1: { span1: undefined, span2: undefined } });
});
act(() => {
result.current.onLoadOptions([{ value: 'service1' } as CascaderOption, { value: 'span1' } as CascaderOption]);
});
await waitForNextUpdate();
expect(result.current.allOptions).toEqual({
service1: { span1: { 'trace1 [10 ms]': 'traceId1' }, span2: undefined },
await waitFor(() => {
expect(result.current.allOptions).toEqual({
service1: { span1: { 'trace1 [10 ms]': 'traceId1' }, span2: undefined },
});
});
});
});

View File

@ -1,5 +1,4 @@
import { fireEvent, screen } from '@testing-library/dom';
import { render } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import React, { useState } from 'react';
import { CoreApp, MutableDataFrame } from '@grafana/data';

View File

@ -11,7 +11,7 @@ import './jquery.flot.events';
import $ from 'jquery';
import { clone, find, flatten, isUndefined, map, max as _max, min as _min, sortBy as _sortBy, toNumber } from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot, Root } from 'react-dom/client';
import {
DataFrame,
@ -76,6 +76,7 @@ class GraphElement {
thresholdManager: ThresholdManager;
timeRegionManager: TimeRegionManager;
declare legendElem: HTMLElement;
declare legendElemRoot: Root;
constructor(
private scope: any,
@ -118,6 +119,7 @@ class GraphElement {
// get graph legend element
if (this.elem && this.elem.parent) {
this.legendElem = this.elem.parent().find('.graph-legend')[0];
this.legendElemRoot = createRoot(this.legendElem);
}
}
@ -134,7 +136,7 @@ class GraphElement {
if (!this.panel.legend.show) {
if (this.legendElem.hasChildNodes()) {
ReactDOM.unmountComponentAtNode(this.legendElem);
this.legendElemRoot.unmount();
}
this.renderPanel();
return;
@ -156,7 +158,10 @@ class GraphElement {
};
const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps);
ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel());
// render callback isn't supported in react 18+, see: https://github.com/reactwg/react-18/discussions/5
this.legendElemRoot.render(legendReactElem);
requestIdleCallback(() => this.renderPanel());
}
onGraphHover(evt: LegacyGraphHoverEventPayload | DataHoverPayload) {
@ -192,7 +197,7 @@ class GraphElement {
this.elem.off();
this.elem.remove();
ReactDOM.unmountComponentAtNode(this.legendElem);
this.legendElemRoot.unmount();
}
onGraphHoverClear(handler: LegacyEventHandler<any>) {

View File

@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import { useLayout } from './layout';
import { EdgeDatum, NodeDatum } from './types';

361
yarn.lock
View File

@ -2993,13 +2993,13 @@ __metadata:
"@grafana/ui": 10.0.0-pre
"@types/jest": 26.0.15
"@types/lodash": 4.14.149
"@types/react": 17.0.30
"@types/react": 18.0.28
jquery: 3.5.1
lodash: 4.17.21
react: 17.0.1
react-dom: 17.0.1
react: 18.2.0
react-dom: 18.2.0
react-hook-form: 7.5.3
react-router-dom: ^5.2.0
react-router-dom: 5.3.3
tslib: 2.4.0
languageName: unknown
linkType: soft
@ -3033,10 +3033,9 @@ __metadata:
"@rollup/plugin-commonjs": 23.0.2
"@rollup/plugin-json": 5.0.1
"@rollup/plugin-node-resolve": 15.0.1
"@testing-library/dom": 8.20.0
"@testing-library/dom": 9.0.1
"@testing-library/jest-dom": 5.16.5
"@testing-library/react": 12.1.4
"@testing-library/react-hooks": 8.0.1
"@testing-library/react": 14.0.0
"@testing-library/user-event": 14.4.3
"@types/d3-interpolate": ^3.0.0
"@types/dompurify": ^2
@ -3047,8 +3046,8 @@ __metadata:
"@types/marked": 4.0.8
"@types/node": 18.14.6
"@types/papaparse": 5.3.7
"@types/react": 17.0.42
"@types/react-dom": 17.0.14
"@types/react": 18.0.28
"@types/react-dom": 18.0.11
"@types/sinon": 10.0.13
"@types/testing-library__jest-dom": 5.14.5
"@types/tinycolor2": 1.4.3
@ -3065,9 +3064,9 @@ __metadata:
moment-timezone: 0.5.41
ol: 7.2.2
papaparse: 5.3.2
react: 17.0.2
react-dom: 17.0.2
react-test-renderer: 17.0.2
react: 18.2.0
react-dom: 18.2.0
react-test-renderer: 18.2.0
react-use: 17.4.0
regenerator-runtime: 0.13.11
rimraf: 4.4.0
@ -3083,8 +3082,8 @@ __metadata:
uplot: 1.6.24
xss: ^1.0.14
peerDependencies:
react: ^16.8.0 || ^17.0.0
react-dom: ^16.8.0 || ^17.0.0
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
languageName: unknown
linkType: soft
@ -3293,22 +3292,21 @@ __metadata:
"@rollup/plugin-commonjs": 23.0.2
"@rollup/plugin-node-resolve": 15.0.1
"@sentry/browser": 6.19.7
"@testing-library/dom": 8.20.0
"@testing-library/react": 12.1.4
"@testing-library/react-hooks": 8.0.1
"@testing-library/dom": 9.0.1
"@testing-library/react": 14.0.0
"@testing-library/user-event": 14.4.3
"@types/angular": 1.8.4
"@types/history": 4.7.11
"@types/jest": 29.2.3
"@types/lodash": 4.14.191
"@types/react": 17.0.42
"@types/react-dom": 17.0.14
"@types/react": 18.0.28
"@types/react-dom": 18.0.11
"@types/systemjs": ^0.20.6
esbuild: 0.16.17
history: 4.10.1
lodash: 4.17.21
react: 17.0.2
react-dom: 17.0.2
react: 18.2.0
react-dom: 18.2.0
rimraf: 4.4.0
rollup: 2.79.1
rollup-plugin-dts: ^5.0.0
@ -3321,8 +3319,8 @@ __metadata:
tslib: 2.5.0
typescript: 4.8.4
peerDependencies:
react: 17.0.2
react-dom: 17.0.2
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
languageName: unknown
linkType: soft
@ -3500,10 +3498,9 @@ __metadata:
"@storybook/preset-scss": 1.0.3
"@storybook/react": 6.5.16
"@storybook/theming": 6.5.16
"@testing-library/dom": 8.20.0
"@testing-library/dom": 9.0.1
"@testing-library/jest-dom": 5.16.5
"@testing-library/react": 12.1.4
"@testing-library/react-hooks": 8.0.1
"@testing-library/react": 14.0.0
"@testing-library/user-event": 14.4.3
"@types/common-tags": ^1.8.0
"@types/d3": 7.4.0
@ -3515,15 +3512,15 @@ __metadata:
"@types/mock-raf": 1.0.3
"@types/node": 18.14.6
"@types/prismjs": 1.26.0
"@types/react": 17.0.42
"@types/react": 18.0.28
"@types/react-beautiful-dnd": 13.1.3
"@types/react-calendar": 3.9.0
"@types/react-color": 3.0.6
"@types/react-dom": 17.0.14
"@types/react-dom": 18.0.11
"@types/react-highlight-words": 0.16.4
"@types/react-router-dom": 5.3.3
"@types/react-table": 7.7.14
"@types/react-test-renderer": 17.0.1
"@types/react-test-renderer": 18.0.0
"@types/react-transition-group": 4.4.5
"@types/react-window": 1.8.5
"@types/slate": 0.47.11
@ -3561,12 +3558,12 @@ __metadata:
rc-slider: 10.1.1
rc-time-picker: ^3.7.3
rc-tooltip: 5.3.1
react: 17.0.2
react: 18.2.0
react-beautiful-dnd: 13.1.1
react-calendar: 4.0.0
react-colorful: 5.6.1
react-custom-scrollbars-2: 4.5.0
react-dom: 17.0.2
react-dom: 18.2.0
react-dropzone: 14.2.3
react-highlight-words: 0.20.0
react-hook-form: 7.5.3
@ -3574,11 +3571,11 @@ __metadata:
react-inlinesvg: 3.0.2
react-popper: 2.3.0
react-popper-tooltip: 4.4.2
react-router-dom: ^5.2.0
react-router-dom: 5.3.3
react-select: 5.7.0
react-select-event: ^5.1.0
react-table: 7.8.0
react-test-renderer: 17.0.2
react-test-renderer: 18.2.0
react-transition-group: 4.4.5
react-use: 17.4.0
react-window: 1.8.8
@ -3603,8 +3600,8 @@ __metadata:
uuid: 9.0.0
webpack: 5.76.0
peerDependencies:
react: ^16.8.0 || ^17.0.0
react-dom: ^16.8.0 || ^17.0.0
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
languageName: unknown
linkType: soft
@ -7315,6 +7312,53 @@ __metadata:
languageName: node
linkType: hard
"@storybook/addon-docs@patch:@storybook/addon-docs@npm:6.5.16#.yarn/patches/@storybook-addon-docs-npm-6.5.16-56ecbd77e7.patch::locator=grafana%40workspace%3A.":
version: 6.5.16
resolution: "@storybook/addon-docs@patch:@storybook/addon-docs@npm%3A6.5.16#.yarn/patches/@storybook-addon-docs-npm-6.5.16-56ecbd77e7.patch::version=6.5.16&hash=1c984c&locator=grafana%40workspace%3A."
dependencies:
"@babel/plugin-transform-react-jsx": ^7.12.12
"@babel/preset-env": ^7.12.11
"@jest/transform": ^26.6.2
"@mdx-js/react": ^1.6.22
"@storybook/addons": 6.5.16
"@storybook/api": 6.5.16
"@storybook/components": 6.5.16
"@storybook/core-common": 6.5.16
"@storybook/core-events": 6.5.16
"@storybook/csf": 0.0.2--canary.4566f4d.1
"@storybook/docs-tools": 6.5.16
"@storybook/mdx1-csf": ^0.0.1
"@storybook/node-logger": 6.5.16
"@storybook/postinstall": 6.5.16
"@storybook/preview-web": 6.5.16
"@storybook/source-loader": 6.5.16
"@storybook/store": 6.5.16
"@storybook/theming": 6.5.16
babel-loader: ^8.0.0
core-js: ^3.8.2
fast-deep-equal: ^3.1.3
global: ^4.4.0
lodash: ^4.17.21
regenerator-runtime: ^0.13.7
remark-external-links: ^8.0.0
remark-slug: ^6.0.0
ts-dedent: ^2.0.0
util-deprecate: ^1.0.2
peerDependencies:
"@storybook/mdx2-csf": ^0.0.3
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@storybook/mdx2-csf":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: de675ef948051275255c1430e114a6bcdb7fabc48f747c3d8d79a062679790208d8c6814809d8747068c5ee8a5f5425ca1c5d87f495fe80bcff99cbd45e72832
languageName: node
linkType: hard
"@storybook/addon-essentials@npm:6.5.16":
version: 6.5.16
resolution: "@storybook/addon-essentials@npm:6.5.16"
@ -8672,9 +8716,9 @@ __metadata:
languageName: node
linkType: hard
"@testing-library/dom@npm:8.20.0, @testing-library/dom@npm:>=7, @testing-library/dom@npm:^8.0.0":
version: 8.20.0
resolution: "@testing-library/dom@npm:8.20.0"
"@testing-library/dom@npm:9.0.1, @testing-library/dom@npm:>=7, @testing-library/dom@npm:^9.0.0":
version: 9.0.1
resolution: "@testing-library/dom@npm:9.0.1"
dependencies:
"@babel/code-frame": ^7.10.4
"@babel/runtime": ^7.12.5
@ -8682,9 +8726,9 @@ __metadata:
aria-query: ^5.0.0
chalk: ^4.1.0
dom-accessibility-api: ^0.5.9
lz-string: ^1.4.4
lz-string: ^1.5.0
pretty-format: ^27.0.2
checksum: 1e599129a2fe91959ce80900a0a4897232b89e2a8e22c1f5950c36d39c97629ea86b4986b60b173b5525a05de33fde1e35836ea597b03de78cc51b122835c6f0
checksum: fa3d4097d0efd3b186d90e32ffa71c3f9c4ea7a5a793b186b565044b2950eea469594e1f75803e5edb52a75731feecd19cf7034b009b0bd3bfba173eb9c9cd0c
languageName: node
linkType: hard
@ -8705,7 +8749,7 @@ __metadata:
languageName: node
linkType: hard
"@testing-library/react-hooks@npm:8.0.1":
"@testing-library/react-hooks@npm:^8.0.1":
version: 8.0.1
resolution: "@testing-library/react-hooks@npm:8.0.1"
dependencies:
@ -8727,17 +8771,17 @@ __metadata:
languageName: node
linkType: hard
"@testing-library/react@npm:12.1.4":
version: 12.1.4
resolution: "@testing-library/react@npm:12.1.4"
"@testing-library/react@npm:14.0.0":
version: 14.0.0
resolution: "@testing-library/react@npm:14.0.0"
dependencies:
"@babel/runtime": ^7.12.5
"@testing-library/dom": ^8.0.0
"@types/react-dom": "*"
"@testing-library/dom": ^9.0.0
"@types/react-dom": ^18.0.0
peerDependencies:
react: "*"
react-dom: "*"
checksum: 944c5f8d4abb22c0650c25c7ae499651828c37c0e741ff67a4635d4cd99b307d486dabec05b372aba638bd359d29cd2af97393b5055ea294a201d80b4bc384b8
react: ^18.0.0
react-dom: ^18.0.0
checksum: 4a54c8f56cc4a39b50803205f84f06280bb76521d6d5d4b3b36651d760c7c7752ef142d857d52aaf4fad4848ed7a8be49afc793a5dda105955d2f8bef24901ac
languageName: node
linkType: hard
@ -9468,13 +9512,6 @@ __metadata:
languageName: node
linkType: hard
"@types/history@npm:*":
version: 4.7.9
resolution: "@types/history@npm:4.7.9"
checksum: 556b062adb92795839301965776b0418e9ca32798bd8a6031345a6a84d7512771107143a497b7e3ff826d0b0d4456e962c6450485dda6f55c7dd33371e840529
languageName: node
linkType: hard
"@types/history@npm:4.7.11, @types/history@npm:^4.7.11":
version: 4.7.11
resolution: "@types/history@npm:4.7.11"
@ -10024,12 +10061,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react-dom@npm:17.0.14":
version: 17.0.14
resolution: "@types/react-dom@npm:17.0.14"
"@types/react-dom@npm:18.0.11, @types/react-dom@npm:^18.0.0":
version: 18.0.11
resolution: "@types/react-dom@npm:18.0.11"
dependencies:
"@types/react": "*"
checksum: b54cd0ef573236b3d87fe7493e6d1c36d8b4ca37a3b46364272a5c91ac178e3296b68ea1aeb299ce68f12ad663c5720ee890d0539b14881c6754bdcbdb0befa0
checksum: 579691e4d5ec09688087568037c35edf8cfb1ab3e07f6c60029280733ee7b5c06d66df6fcc90786702c93ac8cb13bc7ff16c79ddfc75d082938fbaa36e1cdbf4
languageName: node
linkType: hard
@ -10096,12 +10133,12 @@ __metadata:
linkType: hard
"@types/react-router@npm:*":
version: 5.1.17
resolution: "@types/react-router@npm:5.1.17"
version: 5.1.20
resolution: "@types/react-router@npm:5.1.20"
dependencies:
"@types/history": "*"
"@types/history": ^4.7.11
"@types/react": "*"
checksum: b9d1c7b6ce073652c39712d2b02aeec7640036e369c04be2e57e4b0eb049b64ec9f34fb91cad680ab3f794e89576f77aacadb015b61eb21500a1779e5c955b86
checksum: 128764143473a5e9457ddc715436b5d49814b1c214dde48939b9bef23f0e77f52ffcdfa97eb8d3cc27e2c229869c0cdd90f637d887b62f2c9f065a87d6425419
languageName: node
linkType: hard
@ -10114,12 +10151,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react-test-renderer@npm:17.0.1":
version: 17.0.1
resolution: "@types/react-test-renderer@npm:17.0.1"
"@types/react-test-renderer@npm:18.0.0":
version: 18.0.0
resolution: "@types/react-test-renderer@npm:18.0.0"
dependencies:
"@types/react": "*"
checksum: ecaae8df36cd8cfeb89080d52534856acc3789bad9a6e369ff5119426377c827b4e5b5daa638507f2c1c2fd6c994bf45de288a698143178cd4049c2cd8b77b35
checksum: 6afc938a1d7618d88ab8793e251f0bd5981bf3f08c1b600f74df3f8800b92589ea534dc6dcb7c8d683893fcc740bf8d7843a42bf2dae59785cfe88f004bd7b0b
languageName: node
linkType: hard
@ -10169,36 +10206,14 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:*":
version: 17.0.33
resolution: "@types/react@npm:17.0.33"
"@types/react@npm:*, @types/react@npm:18.0.28":
version: 18.0.28
resolution: "@types/react@npm:18.0.28"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: 5f53f3dae034229ff1eccb5de1c16d046696e883f8eae81b86e95c532798e655015e4edff8a6c9ec8f1c0ef12bb60dec43622d06256ba33ba6069e67a74b8d4e
languageName: node
linkType: hard
"@types/react@npm:17.0.30":
version: 17.0.30
resolution: "@types/react@npm:17.0.30"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: e3aaac1b8fda6e3622b75db0bd7d8dc412c2f2b77a00afdd32cae8c71fb0b1ca6926ab1fbe1c536dd51d96c0ba372738993837a8df1637637aaab7b86e421b7f
languageName: node
linkType: hard
"@types/react@npm:17.0.42":
version: 17.0.42
resolution: "@types/react@npm:17.0.42"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: 9a84374da1173901b5eba6b2dda9e70220842c8e70274628e5c93684fca59786b7f6215c93f492e34f8834983de096b2544f280c60da9b8fd4c3f20ffe7cc51e
checksum: e752df961105e5127652460504785897ca6e77259e0da8f233f694f9e8f451cde7fa0709d4456ade0ff600c8ce909cfe29f9b08b9c247fa9b734e126ec53edd7
languageName: node
linkType: hard
@ -20160,10 +20175,10 @@ __metadata:
"@sentry/utils": 6.19.7
"@swc/core": 1.3.38
"@swc/helpers": 0.4.14
"@testing-library/dom": 8.20.0
"@testing-library/dom": 9.0.1
"@testing-library/jest-dom": 5.16.5
"@testing-library/react": 12.1.4
"@testing-library/react-hooks": 8.0.1
"@testing-library/react": 14.0.0
"@testing-library/react-hooks": ^8.0.1
"@testing-library/user-event": 14.4.3
"@types/angular": 1.8.4
"@types/angular-route": 1.7.2
@ -20192,16 +20207,16 @@ __metadata:
"@types/papaparse": 5.3.7
"@types/pluralize": ^0.0.29
"@types/prismjs": 1.26.0
"@types/react": 17.0.42
"@types/react": 18.0.28
"@types/react-beautiful-dnd": 13.1.3
"@types/react-dom": 17.0.14
"@types/react-dom": 18.0.11
"@types/react-grid-layout": 1.3.2
"@types/react-highlight-words": 0.16.4
"@types/react-redux": 7.1.25
"@types/react-resizable": 3.0.3
"@types/react-router-dom": 5.3.3
"@types/react-table": 7.7.14
"@types/react-test-renderer": 17.0.1
"@types/react-test-renderer": 18.0.0
"@types/react-transition-group": 4.4.5
"@types/react-virtualized-auto-sizer": 1.0.1
"@types/react-window": 1.8.5
@ -20341,11 +20356,11 @@ __metadata:
rc-time-picker: 3.7.3
rc-tree: 5.7.2
re-resizable: 6.9.9
react: 17.0.2
react: 18.2.0
react-awesome-query-builder: 5.4.0
react-beautiful-dnd: 13.1.1
react-diff-viewer: ^3.1.1
react-dom: 17.0.2
react-dom: 18.2.0
react-draggable: 4.4.5
react-dropzone: ^14.2.3
react-enable: ^3.1.0
@ -20361,13 +20376,13 @@ __metadata:
react-refresh: 0.14.0
react-resizable: 3.0.4
react-reverse-portal: 2.1.1
react-router-dom: ^5.2.0
react-router-dom: 5.3.3
react-select: 5.7.0
react-select-event: 5.5.1
react-simple-compat: 1.2.3
react-split-pane: 0.1.92
react-table: 7.8.0
react-test-renderer: 17.0.2
react-test-renderer: 18.2.0
react-transition-group: 4.4.5
react-use: 17.4.0
react-virtual: 2.10.4
@ -25165,12 +25180,12 @@ __metadata:
languageName: node
linkType: hard
"lz-string@npm:^1.4.4":
version: 1.4.4
resolution: "lz-string@npm:1.4.4"
"lz-string@npm:^1.5.0":
version: 1.5.0
resolution: "lz-string@npm:1.5.0"
bin:
lz-string: bin/bin.js
checksum: 54e31238a61a84d8f664d9860a9fba7310c5b97a52c444f80543069bc084815eff40b8d4474ae1d93992fdf6c252dca37cf27f6adbeb4dbc3df2f3ac773d0e61
checksum: 1ee98b4580246fd90dd54da6e346fb1caefcf05f677c686d9af237a157fdea3fd7c83a4bc58f858cd5b10a34d27afe0fdcbd0505a47e0590726a873dc8b8f65d
languageName: node
linkType: hard
@ -30810,29 +30825,15 @@ __metadata:
languageName: node
linkType: hard
"react-dom@npm:17.0.1":
version: 17.0.1
resolution: "react-dom@npm:17.0.1"
"react-dom@npm:18.2.0":
version: 18.2.0
resolution: "react-dom@npm:18.2.0"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
scheduler: ^0.20.1
scheduler: ^0.23.0
peerDependencies:
react: 17.0.1
checksum: df2af300dd4f49a5daaccc38f5a307def2a9ae2b7ebffa3dce8fb9986129057696b86a2c94e5ae36133057c69428c500e4ee3bf5884eb44e5632ace8b7ace41f
languageName: node
linkType: hard
"react-dom@npm:17.0.2":
version: 17.0.2
resolution: "react-dom@npm:17.0.2"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
scheduler: ^0.20.2
peerDependencies:
react: 17.0.2
checksum: 1c1eaa3bca7c7228d24b70932e3d7c99e70d1d04e13bb0843bbf321582bc25d7961d6b8a6978a58a598af2af496d1cedcfb1bf65f6b0960a0a8161cb8dab743c
react: ^18.2.0
checksum: 7d323310bea3a91be2965f9468d552f201b1c27891e45ddc2d6b8f717680c95a75ae0bc1e3f5cf41472446a2589a75aed4483aee8169287909fcd59ad149e8cc
languageName: node
linkType: hard
@ -30905,13 +30906,13 @@ __metadata:
linkType: hard
"react-error-boundary@npm:^3.1.0":
version: 3.1.3
resolution: "react-error-boundary@npm:3.1.3"
version: 3.1.4
resolution: "react-error-boundary@npm:3.1.4"
dependencies:
"@babel/runtime": ^7.12.5
peerDependencies:
react: ">=16.13.1"
checksum: 0a05af404aa054c54d7bc65a1814810093bf136c3ad4b3576a51d8509ee8fc302adfb66405da501fc01e839db557dd0d994b487e651897e36293907bb61458cf
checksum: f36270a5d775a25c8920f854c0d91649ceea417b15b5bc51e270a959b0476647bb79abb4da3be7dd9a4597b029214e8fe43ea914a7f16fa7543c91f784977f1b
languageName: node
linkType: hard
@ -31030,13 +31031,20 @@ __metadata:
languageName: node
linkType: hard
"react-is@npm:17.0.2, react-is@npm:^16.12.0 || ^17.0.0, react-is@npm:^17.0.1, react-is@npm:^17.0.2":
"react-is@npm:17.0.2, react-is@npm:^17.0.1, react-is@npm:^17.0.2":
version: 17.0.2
resolution: "react-is@npm:17.0.2"
checksum: 9d6d111d8990dc98bc5402c1266a808b0459b5d54830bbea24c12d908b536df7883f268a7868cfaedde3dd9d4e0d574db456f84d2e6df9c4526f99bb4b5344d8
languageName: node
linkType: hard
"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.2.0":
version: 18.2.0
resolution: "react-is@npm:18.2.0"
checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e
languageName: node
linkType: hard
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@ -31184,26 +31192,26 @@ __metadata:
languageName: node
linkType: hard
"react-router-dom@npm:^5.2.0":
version: 5.3.0
resolution: "react-router-dom@npm:5.3.0"
"react-router-dom@npm:5.3.3":
version: 5.3.3
resolution: "react-router-dom@npm:5.3.3"
dependencies:
"@babel/runtime": ^7.12.13
history: ^4.9.0
loose-envify: ^1.3.1
prop-types: ^15.6.2
react-router: 5.2.1
react-router: 5.3.3
tiny-invariant: ^1.0.2
tiny-warning: ^1.0.0
peerDependencies:
react: ">=15"
checksum: 47584fd629ecca52398d7888cab193b8a74057cc99a7ef44410c405d4082f618c3c0399db5325bc3524f9c511404086169570013b61a94dfa6acdfdc850d7a1f
checksum: e1998918e391611f09b967bce0cb88bc9794aa3d8dc5f86453467a1226ae2ace648a1f401f5282f19c84a3a61fa6a3207e2a6fdfe8c8efae0b255244631febeb
languageName: node
linkType: hard
"react-router@npm:5.2.1":
version: 5.2.1
resolution: "react-router@npm:5.2.1"
"react-router@npm:5.3.3":
version: 5.3.3
resolution: "react-router@npm:5.3.3"
dependencies:
"@babel/runtime": ^7.12.13
history: ^4.9.0
@ -31217,7 +31225,7 @@ __metadata:
tiny-warning: ^1.0.0
peerDependencies:
react: ">=15"
checksum: 7daae084bf64531eb619cc5f4cc40ce5ae0a541b64f71d74ec71a38cbf6130ebdbb7cf38f157303fad5846deec259401f96c4d6c7386466dcc989719e01f9aaa
checksum: 52a9f28fa97577fda18a8ed2922b658704eafe873e444fe07202640d55d9e81e67c03efd2b2a5b80e3a80e8be8352df826a227ce5f42b33b91bef853c74d4841
languageName: node
linkType: hard
@ -31259,15 +31267,15 @@ __metadata:
languageName: node
linkType: hard
"react-shallow-renderer@npm:^16.13.1":
version: 16.14.1
resolution: "react-shallow-renderer@npm:16.14.1"
"react-shallow-renderer@npm:^16.15.0":
version: 16.15.0
resolution: "react-shallow-renderer@npm:16.15.0"
dependencies:
object-assign: ^4.1.1
react-is: ^16.12.0 || ^17.0.0
react-is: ^16.12.0 || ^17.0.0 || ^18.0.0
peerDependencies:
react: ^16.0.0 || ^17.0.0
checksum: f344c663c48720d19559b4198b1f63ad47a3f11bedc92ede053a6c0706b5209e6110086f3ccc6db04eda9f0d1a415845956ddfb6ce09a922167d4831fcba9314
react: ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 6052c7e3e9627485120ebd8257f128aad8f56386fe8d42374b7743eac1be457c33506d153c7886b4e32923c0c352d402ab805ef9ca02dbcd8393b2bdeb6e5af8
languageName: node
linkType: hard
@ -31307,6 +31315,20 @@ __metadata:
languageName: node
linkType: hard
"react-split-pane@patch:react-split-pane@npm:0.1.92#.yarn/patches/react-split-pane-npm-0.1.92-93dbf51dff.patch::locator=grafana%40workspace%3A.":
version: 0.1.92
resolution: "react-split-pane@patch:react-split-pane@npm%3A0.1.92#.yarn/patches/react-split-pane-npm-0.1.92-93dbf51dff.patch::version=0.1.92&hash=23e19a&locator=grafana%40workspace%3A."
dependencies:
prop-types: ^15.7.2
react-lifecycles-compat: ^3.0.4
react-style-proptype: ^3.2.2
peerDependencies:
react: ^16.0.0-0
react-dom: ^16.0.0-0
checksum: 4cdcc9e5a03ee1534100ddd6d5a5cce9e083ffe1a0acf7adf085d71d003d0ac475192964e0d63b2887392871dd687ec5340e2122eb588b5685de0a219cb00b11
languageName: node
linkType: hard
"react-style-proptype@npm:^3.2.2":
version: 3.2.2
resolution: "react-style-proptype@npm:3.2.2"
@ -31340,17 +31362,16 @@ __metadata:
languageName: node
linkType: hard
"react-test-renderer@npm:17.0.2":
version: 17.0.2
resolution: "react-test-renderer@npm:17.0.2"
"react-test-renderer@npm:18.2.0":
version: 18.2.0
resolution: "react-test-renderer@npm:18.2.0"
dependencies:
object-assign: ^4.1.1
react-is: ^17.0.2
react-shallow-renderer: ^16.13.1
scheduler: ^0.20.2
react-is: ^18.2.0
react-shallow-renderer: ^16.15.0
scheduler: ^0.23.0
peerDependencies:
react: 17.0.2
checksum: e6b5c6ed2a0bde2c34f1ab9523ff9bc4c141a271daf730d6b852374e83acc0155d58ab71a318251e953ebfa65b8bebb9c5dce3eba1ccfcbef7cc4e1e8261c401
react: ^18.2.0
checksum: 6b6980ced93fa2b72662d5e4ab3b4896833586940047ce52ca9aca801e5432adf05fcbe28289b0af3ce6a2a7c590974e25dcc8aa43d0de658bfe8bbcd686f958
languageName: node
linkType: hard
@ -31475,23 +31496,12 @@ __metadata:
languageName: node
linkType: hard
"react@npm:17.0.1":
version: 17.0.1
resolution: "react@npm:17.0.1"
"react@npm:18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
checksum: 83b9df9529a2b489f00a4eaa608fc7d55518b258e046c100344ae068713e43ae64e477a140f87e38cfe75489bcfd26d27fce5818f89f4ec41bdbda7ead4bb426
languageName: node
linkType: hard
"react@npm:17.0.2":
version: 17.0.2
resolution: "react@npm:17.0.2"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
checksum: b254cc17ce3011788330f7bbf383ab653c6848902d7936a87b09d835d091e3f295f7e9dd1597c6daac5dc80f90e778c8230218ba8ad599f74adcc11e33b9d61b
checksum: 88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b
languageName: node
linkType: hard
@ -32799,13 +32809,12 @@ __metadata:
languageName: node
linkType: hard
"scheduler@npm:^0.20.1, scheduler@npm:^0.20.2":
version: 0.20.2
resolution: "scheduler@npm:0.20.2"
"scheduler@npm:^0.23.0":
version: 0.23.0
resolution: "scheduler@npm:0.23.0"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
checksum: c4b35cf967c8f0d3e65753252d0f260271f81a81e427241295c5a7b783abf4ea9e905f22f815ab66676f5313be0a25f47be582254db8f9241b259213e999b8fc
checksum: d79192eeaa12abef860c195ea45d37cbf2bbf5f66e3c4dcd16f54a7da53b17788a70d109ee3d3dde1a0fd50e6a8fc171f4300356c5aee4fc0171de526bf35f8a
languageName: node
linkType: hard