Compare commits
69 Commits
sriram/SQL
...
ash/react-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7577f940 | ||
|
|
21ea3f2c2e | ||
|
|
59d3ef2504 | ||
|
|
ff7bab5753 | ||
|
|
9ba65ab73f | ||
|
|
d97c56c51e | ||
|
|
4752b45f81 | ||
|
|
43ce2acafc | ||
|
|
f2cb70b1e0 | ||
|
|
7a1938c23b | ||
|
|
03de5f59e6 | ||
|
|
494a663449 | ||
|
|
d9059ca7b2 | ||
|
|
c254cf1387 | ||
|
|
ebf6ba442b | ||
|
|
462b6354d0 | ||
|
|
7f1f3c6ba6 | ||
|
|
de6d2700b7 | ||
|
|
481dc3e630 | ||
|
|
0f46f38b77 | ||
|
|
2f3a4d0358 | ||
|
|
d0aec88ca8 | ||
|
|
4a0e9204b3 | ||
|
|
12c6e9615f | ||
|
|
7a0d7c5dec | ||
|
|
b035732a85 | ||
|
|
05ef468b41 | ||
|
|
730f10597a | ||
|
|
caff0e2d1e | ||
|
|
f10a494369 | ||
|
|
8d42d4a079 | ||
|
|
720f038981 | ||
|
|
b380ce2bfd | ||
|
|
68a83b73c9 | ||
|
|
3808ddf948 | ||
|
|
f1d654d2e3 | ||
|
|
b0798f24c5 | ||
|
|
4c90d10281 | ||
|
|
96614c4eca | ||
|
|
fd4a97e49e | ||
|
|
68e0ed782c | ||
|
|
5fbbf2ac4a | ||
|
|
e97d48d86b | ||
|
|
74c656713a | ||
|
|
470cd869f3 | ||
|
|
3ef28b727f | ||
|
|
afe54f6739 | ||
|
|
f7d8fd4986 | ||
|
|
c73db56467 | ||
|
|
37bd5ded3a | ||
|
|
418c1a4d5a | ||
|
|
d3e807d6e2 | ||
|
|
03a044a9a0 | ||
|
|
e861318c2d | ||
|
|
35633b756d | ||
|
|
eb77bf89df | ||
|
|
636c62862d | ||
|
|
737ee7c7bd | ||
|
|
13b5c3f974 | ||
|
|
1915a92eb2 | ||
|
|
94cad60654 | ||
|
|
9d9085075b | ||
|
|
efeac25952 | ||
|
|
0da94b11ee | ||
|
|
9aa86eb056 | ||
|
|
f6107150e0 | ||
|
|
cb90eddf84 | ||
|
|
141ed7bdbf | ||
|
|
d2bf550499 |
24
.github/workflows/pr-e2e-tests.yml
vendored
24
.github/workflows/pr-e2e-tests.yml
vendored
@@ -192,6 +192,30 @@ jobs:
|
|||||||
-f "output[summary]=${IMAGE}" \
|
-f "output[summary]=${IMAGE}" \
|
||||||
-f "output[text]=${IMAGE}"
|
-f "output[text]=${IMAGE}"
|
||||||
|
|
||||||
|
# TODO remove this when delivering
|
||||||
|
# This will push the temporary docker image to dockerhub
|
||||||
|
push-docker-image-to-dockerhub:
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build-grafana
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||||
|
with:
|
||||||
|
name: grafana-docker-tar-gz
|
||||||
|
path: .
|
||||||
|
- uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2
|
||||||
|
- name: Load & Push Docker image
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
LOADED_IMAGE_NAME=$(docker load -i grafana.docker.tar.gz | sed 's/Loaded image: //g')
|
||||||
|
DOCKER_IMAGE="grafana/grafana:dev-preview-react19"
|
||||||
|
docker tag "${LOADED_IMAGE_NAME}" "${DOCKER_IMAGE}"
|
||||||
|
docker push "${DOCKER_IMAGE}"
|
||||||
|
|
||||||
run-e2e-tests:
|
run-e2e-tests:
|
||||||
needs:
|
needs:
|
||||||
- build-grafana
|
- build-grafana
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"@types/lodash": "4.17.7",
|
"@types/lodash": "4.17.7",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.9.2",
|
||||||
"@types/prismjs": "1.26.4",
|
"@types/prismjs": "1.26.4",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@types/uuid": "9.0.8",
|
"@types/uuid": "9.0.8",
|
||||||
"glob": "10.5.0",
|
"glob": "10.5.0",
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
"@grafana/runtime": "workspace:*",
|
"@grafana/runtime": "workspace:*",
|
||||||
"@grafana/schema": "workspace:*",
|
"@grafana/schema": "workspace:*",
|
||||||
"@grafana/ui": "workspace:*",
|
"@grafana/ui": "workspace:*",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-router-dom": "^6.22.0",
|
"react-router-dom": "^6.22.0",
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.1",
|
||||||
"tslib": "2.6.3"
|
"tslib": "2.6.3"
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"@types/lodash": "4.17.7",
|
"@types/lodash": "4.17.7",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.9.2",
|
||||||
"@types/prismjs": "1.26.4",
|
"@types/prismjs": "1.26.4",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@types/uuid": "9.0.8",
|
"@types/uuid": "9.0.8",
|
||||||
"glob": "10.4.1",
|
"glob": "10.4.1",
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
"@grafana/runtime": "workspace:*",
|
"@grafana/runtime": "workspace:*",
|
||||||
"@grafana/schema": "workspace:*",
|
"@grafana/schema": "workspace:*",
|
||||||
"@grafana/ui": "workspace:*",
|
"@grafana/ui": "workspace:*",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-router-dom": "^6.22.0",
|
"react-router-dom": "^6.22.0",
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.1",
|
||||||
"tslib": "2.6.3"
|
"tslib": "2.6.3"
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ test.describe(
|
|||||||
test('Graph panel is auto-migrated', async ({ gotoDashboardPage, page }) => {
|
test('Graph panel is auto-migrated', async ({ gotoDashboardPage, page }) => {
|
||||||
await gotoDashboardPage({ uid: DASHBOARD_ID });
|
await gotoDashboardPage({ uid: DASHBOARD_ID });
|
||||||
await expect(page.getByText(DASHBOARD_NAME)).toBeVisible();
|
await expect(page.getByText(DASHBOARD_NAME)).toBeVisible();
|
||||||
await expect(page.getByTestId(UPLOT_MAIN_DIV_SELECTOR).first()).toBeHidden();
|
|
||||||
|
|
||||||
await gotoDashboardPage({ uid: DASHBOARD_ID });
|
|
||||||
|
|
||||||
await expect(page.getByTestId(UPLOT_MAIN_DIV_SELECTOR).first()).toBeVisible();
|
await expect(page.getByTestId(UPLOT_MAIN_DIV_SELECTOR).first()).toBeVisible();
|
||||||
});
|
});
|
||||||
@@ -24,9 +21,6 @@ test.describe(
|
|||||||
test('Annotation markers exist for time regions', async ({ gotoDashboardPage, selectors, page }) => {
|
test('Annotation markers exist for time regions', async ({ gotoDashboardPage, selectors, page }) => {
|
||||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_ID });
|
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_ID });
|
||||||
await expect(page.getByText(DASHBOARD_NAME)).toBeVisible();
|
await expect(page.getByText(DASHBOARD_NAME)).toBeVisible();
|
||||||
await expect(page.getByTestId(UPLOT_MAIN_DIV_SELECTOR).first()).toBeHidden();
|
|
||||||
|
|
||||||
await gotoDashboardPage({ uid: DASHBOARD_ID });
|
|
||||||
|
|
||||||
// Check Business Hours panel
|
// Check Business Hours panel
|
||||||
const businessHoursPanel = dashboardPage.getByGrafanaSelector(
|
const businessHoursPanel = dashboardPage.getByGrafanaSelector(
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -140,8 +140,8 @@
|
|||||||
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0",
|
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0",
|
||||||
"@types/pluralize": "^0.0.33",
|
"@types/pluralize": "^0.0.33",
|
||||||
"@types/prismjs": "1.26.5",
|
"@types/prismjs": "1.26.5",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/react-grid-layout": "1.3.5",
|
"@types/react-grid-layout": "1.3.5",
|
||||||
"@types/react-highlight-words": "0.20.0",
|
"@types/react-highlight-words": "0.20.0",
|
||||||
"@types/react-resizable": "3.0.8",
|
"@types/react-resizable": "3.0.8",
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"publint": "^0.3.12",
|
"publint": "^0.3.12",
|
||||||
"react-refresh": "0.14.0",
|
"react-refresh": "0.18.0",
|
||||||
"react-select-event": "5.5.1",
|
"react-select-event": "5.5.1",
|
||||||
"redux-mock-store": "1.5.5",
|
"redux-mock-store": "1.5.5",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
@@ -389,9 +389,9 @@
|
|||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"prismjs": "1.30.0",
|
"prismjs": "1.30.0",
|
||||||
"re-resizable": "6.11.2",
|
"re-resizable": "6.11.2",
|
||||||
"react": "18.3.1",
|
"react": "19.2.1",
|
||||||
"react-diff-viewer-continued": "^3.4.0",
|
"react-diff-viewer-continued": "^3.4.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.1",
|
||||||
"react-draggable": "4.5.0",
|
"react-draggable": "4.5.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch",
|
"react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch",
|
||||||
@@ -462,7 +462,10 @@
|
|||||||
"js-yaml@npm:4.1.0": "^4.1.0",
|
"js-yaml@npm:4.1.0": "^4.1.0",
|
||||||
"js-yaml@npm:=4.1.0": "^4.1.0",
|
"js-yaml@npm:=4.1.0": "^4.1.0",
|
||||||
"nodemailer": "7.0.11",
|
"nodemailer": "7.0.11",
|
||||||
"@storybook/core@npm:8.6.2": "patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch"
|
"@storybook/core@npm:8.6.2": "patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch",
|
||||||
|
"pretty-format/react-is": "19.0.0",
|
||||||
|
"react": "19.2.1",
|
||||||
|
"react-dom": "19.2.1"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
|
|||||||
@@ -70,13 +70,13 @@
|
|||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/lodash": "^4",
|
"@types/lodash": "^4",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/tinycolor2": "^1",
|
"@types/tinycolor2": "^1",
|
||||||
"i18next": "^25.5.2",
|
"i18next": "^25.5.2",
|
||||||
"i18next-cli": "^1.24.22",
|
"i18next-cli": "^1.24.22",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
|
|||||||
@@ -91,12 +91,12 @@
|
|||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/papaparse": "5.3.16",
|
"@types/papaparse": "5.3.16",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"esbuild": "0.25.8",
|
"esbuild": "0.25.8",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
"rollup-plugin-esbuild": "6.2.1",
|
"rollup-plugin-esbuild": "6.2.1",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"@leeoniya/ufuzzy": "1.0.19",
|
"@leeoniya/ufuzzy": "1.0.19",
|
||||||
"d3": "^7.8.5",
|
"d3": "^7.8.5",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-use": "17.6.0",
|
"react-use": "17.6.0",
|
||||||
"react-virtualized-auto-sizer": "1.0.26",
|
"react-virtualized-auto-sizer": "1.0.26",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"@types/jest": "^29.5.4",
|
"@types/jest": "^29.5.4",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-virtualized-auto-sizer": "1.0.8",
|
"@types/react-virtualized-auto-sizer": "1.0.8",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"babel-jest": "29.7.0",
|
"babel-jest": "29.7.0",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { getBarColorByDiff, getBarColorByPackage, getBarColorByValue } from './c
|
|||||||
import { CollapseConfig, CollapsedMap, FlameGraphDataContainer, LevelItem } from './dataTransform';
|
import { CollapseConfig, CollapsedMap, FlameGraphDataContainer, LevelItem } from './dataTransform';
|
||||||
|
|
||||||
type RenderOptions = {
|
type RenderOptions = {
|
||||||
canvasRef: RefObject<HTMLCanvasElement>;
|
canvasRef: RefObject<HTMLCanvasElement | null>;
|
||||||
data: FlameGraphDataContainer;
|
data: FlameGraphDataContainer;
|
||||||
root: LevelItem;
|
root: LevelItem;
|
||||||
direction: 'children' | 'parents';
|
direction: 'children' | 'parents';
|
||||||
@@ -373,7 +373,7 @@ function useColorFunction(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useSetupCanvas(canvasRef: RefObject<HTMLCanvasElement>, wrapperWidth: number, numberOfLevels: number) {
|
function useSetupCanvas(canvasRef: RefObject<HTMLCanvasElement | null>, wrapperWidth: number, numberOfLevels: number) {
|
||||||
const [ctx, setCtx] = useState<CanvasRenderingContext2D>();
|
const [ctx, setCtx] = useState<CanvasRenderingContext2D>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
"react-i18next": "^15.0.0"
|
"react-i18next": "^15.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
"rollup-plugin-copy": "3.5.0",
|
"rollup-plugin-copy": "3.5.0",
|
||||||
"typescript": "5.9.2"
|
"typescript": "5.9.2"
|
||||||
|
|||||||
@@ -36,10 +36,10 @@
|
|||||||
"@testing-library/user-event": "14.6.1",
|
"@testing-library/user-event": "14.6.1",
|
||||||
"@types/jest": "^29.5.4",
|
"@types/jest": "^29.5.4",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/systemjs": "6.15.3",
|
"@types/systemjs": "6.15.3",
|
||||||
"jest": "^29.6.4",
|
"jest": "^29.6.4",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"ts-jest": "29.4.0",
|
"ts-jest": "29.4.0",
|
||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
"typescript": "5.9.2"
|
"typescript": "5.9.2"
|
||||||
|
|||||||
@@ -84,3 +84,19 @@ global.ResizeObserver = class ResizeObserver {
|
|||||||
this.#isObserving = false;
|
this.#isObserving = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
global.MessageChannel = jest.fn().mockImplementation(() => {
|
||||||
|
let onmessage;
|
||||||
|
return {
|
||||||
|
port1: {
|
||||||
|
set onmessage(cb) {
|
||||||
|
onmessage = cb;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
port2: {
|
||||||
|
postMessage: (data) => {
|
||||||
|
onmessage?.({ data });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "12.4.0-pre",
|
"version": "12.4.0-pre",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"terser-webpack-plugin": "5.3.14",
|
"terser-webpack-plugin": "5.3.14",
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
},
|
},
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"@swc/helpers": "^0.5.0",
|
"@swc/helpers": "^0.5.0",
|
||||||
"@swc/jest": "^0.2.26",
|
"@swc/jest": "^0.2.26",
|
||||||
"@types/eslint": "9.6.1",
|
"@types/eslint": "9.6.1",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/webpack-bundle-analyzer": "^4.7.0",
|
"@types/webpack-bundle-analyzer": "^4.7.0",
|
||||||
"copy-webpack-plugin": "13.0.0",
|
"copy-webpack-plugin": "13.0.0",
|
||||||
"eslint": "9.32.0",
|
"eslint": "9.32.0",
|
||||||
|
|||||||
@@ -64,8 +64,8 @@
|
|||||||
"@reduxjs/toolkit": "2.10.1",
|
"@reduxjs/toolkit": "2.10.1",
|
||||||
"@types/debounce-promise": "3.1.9",
|
"@types/debounce-promise": "3.1.9",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/react-highlight-words": "0.20.0",
|
"@types/react-highlight-words": "0.20.0",
|
||||||
"@types/react-window": "1.8.8",
|
"@types/react-window": "1.8.8",
|
||||||
"@types/semver": "7.7.1",
|
"@types/semver": "7.7.1",
|
||||||
@@ -101,8 +101,8 @@
|
|||||||
"i18next-cli": "^1.24.22",
|
"i18next-cli": "^1.24.22",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-environment-jsdom": "29.7.0",
|
"jest-environment-jsdom": "29.7.0",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-select-event": "5.5.1",
|
"react-select-event": "5.5.1",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
|
|
||||||
import { TimeRange } from '@grafana/data';
|
import { TimeRange } from '@grafana/data';
|
||||||
@@ -225,7 +226,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
|
|||||||
newSelectedMetric === '' ? undefined : selector
|
newSelectedMetric === '' ? undefined : selector
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO why?!
|
||||||
|
flushSync(() => {
|
||||||
setMetrics(fetchedMetrics);
|
setMetrics(fetchedMetrics);
|
||||||
|
});
|
||||||
setSelectedMetric(newSelectedMetric);
|
setSelectedMetric(newSelectedMetric);
|
||||||
setLabelKeys(fetchedLabelKeys);
|
setLabelKeys(fetchedLabelKeys);
|
||||||
setIsLoadingLabelKeys(false);
|
setIsLoadingLabelKeys(false);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ describe('MetricsLabelsSection', () => {
|
|||||||
onBlur: onBlur,
|
onBlur: onBlur,
|
||||||
variableEditor: undefined,
|
variableEditor: undefined,
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ describe('MetricsLabelsSection', () => {
|
|||||||
labelsFilters: defaultQuery.labels,
|
labelsFilters: defaultQuery.labels,
|
||||||
variableEditor: undefined,
|
variableEditor: undefined,
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -78,12 +78,12 @@
|
|||||||
"@types/history": "4.7.11",
|
"@types/history": "4.7.11",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"esbuild": "0.25.8",
|
"esbuild": "0.25.8",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
"rollup-plugin-esbuild": "6.2.1",
|
"rollup-plugin-esbuild": "6.2.1",
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
"@react-awesome-query-builder/ui": "6.6.15",
|
"@react-awesome-query-builder/ui": "6.6.15",
|
||||||
"immutable": "5.1.4",
|
"immutable": "5.1.4",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-select": "5.10.2",
|
"react-select": "5.10.2",
|
||||||
"react-use": "17.6.0",
|
"react-use": "17.6.0",
|
||||||
"react-virtualized-auto-sizer": "1.0.26",
|
"react-virtualized-auto-sizer": "1.0.26",
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
"@types/jest": "^29.5.4",
|
"@types/jest": "^29.5.4",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/react-virtualized-auto-sizer": "1.0.8",
|
"@types/react-virtualized-auto-sizer": "1.0.8",
|
||||||
"@types/systemjs": "6.15.3",
|
"@types/systemjs": "6.15.3",
|
||||||
"@types/uuid": "10.0.0",
|
"@types/uuid": "10.0.0",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"@hello-pangea/dnd": "18.0.1",
|
"@hello-pangea/dnd": "18.0.1",
|
||||||
"@monaco-editor/react": "4.7.0",
|
"@monaco-editor/react": "4.7.0",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
|
"@rc-component/cascader": "1.9.0",
|
||||||
"@rc-component/drawer": "1.3.0",
|
"@rc-component/drawer": "1.3.0",
|
||||||
"@rc-component/picker": "1.7.1",
|
"@rc-component/picker": "1.7.1",
|
||||||
"@rc-component/slider": "1.0.1",
|
"@rc-component/slider": "1.0.1",
|
||||||
@@ -105,7 +106,6 @@
|
|||||||
"monaco-editor": "0.34.1",
|
"monaco-editor": "0.34.1",
|
||||||
"ol": "10.7.0",
|
"ol": "10.7.0",
|
||||||
"prismjs": "1.30.0",
|
"prismjs": "1.30.0",
|
||||||
"rc-cascader": "3.34.0",
|
|
||||||
"react-calendar": "^6.0.0",
|
"react-calendar": "^6.0.0",
|
||||||
"react-colorful": "5.6.1",
|
"react-colorful": "5.6.1",
|
||||||
"react-custom-scrollbars-2": "4.5.0",
|
"react-custom-scrollbars-2": "4.5.0",
|
||||||
@@ -167,9 +167,9 @@
|
|||||||
"@types/mock-raf": "1.0.6",
|
"@types/mock-raf": "1.0.6",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/prismjs": "1.26.5",
|
"@types/prismjs": "1.26.5",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-color": "3.0.13",
|
"@types/react-color": "3.0.13",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/react-highlight-words": "0.20.0",
|
"@types/react-highlight-words": "0.20.0",
|
||||||
"@types/react-transition-group": "4.4.12",
|
"@types/react-transition-group": "4.4.12",
|
||||||
"@types/react-window": "1.8.8",
|
"@types/react-window": "1.8.8",
|
||||||
@@ -190,8 +190,8 @@
|
|||||||
"msw": "^2.10.2",
|
"msw": "^2.10.2",
|
||||||
"msw-storybook-addon": "^2.0.5",
|
"msw-storybook-addon": "^2.0.5",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-select-event": "^5.1.0",
|
"react-select-event": "^5.1.0",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import RCCascader, { FieldNames } from 'rc-cascader';
|
import RCCascader, { FieldNames } from '@rc-component/cascader';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
@@ -47,7 +47,7 @@ export const ButtonCascader = (props: ButtonCascaderProps) => {
|
|||||||
<RCCascader
|
<RCCascader
|
||||||
onChange={onChangeCascader(onChange)}
|
onChange={onChangeCascader(onChange)}
|
||||||
loadData={onLoadDataCascader(loadData)}
|
loadData={onLoadDataCascader(loadData)}
|
||||||
dropdownClassName={cx(cascaderStyles.dropdown, styles.popup)}
|
popupClassName={cx(cascaderStyles.dropdown, styles.popup)}
|
||||||
{...rest}
|
{...rest}
|
||||||
expandIcon={null}
|
expandIcon={null}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
import RCCascader from '@rc-component/cascader';
|
||||||
import memoize from 'micro-memoize';
|
import memoize from 'micro-memoize';
|
||||||
import RCCascader from 'rc-cascader';
|
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
@@ -279,7 +279,7 @@ class UnthemedCascader extends PureComponent<CascaderProps, CascaderState> {
|
|||||||
expandIcon={null}
|
expandIcon={null}
|
||||||
open={this.props.alwaysOpen}
|
open={this.props.alwaysOpen}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
dropdownClassName={styles.dropdown}
|
popupClassName={styles.dropdown}
|
||||||
>
|
>
|
||||||
<div className={disableDivFocus}>
|
<div className={disableDivFocus}>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BaseOptionType as RCCascaderOption, CascaderProps } from 'rc-cascader';
|
import { BaseOptionType as RCCascaderOption, CascaderProps } from '@rc-component/cascader';
|
||||||
|
|
||||||
import { CascaderOption } from './Cascader';
|
import { CascaderOption } from './Cascader';
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface ComboboxListProps<T extends string | number> {
|
|||||||
options: Array<ComboboxOption<T>>;
|
options: Array<ComboboxOption<T>>;
|
||||||
highlightedIndex: number | null;
|
highlightedIndex: number | null;
|
||||||
selectedItems?: Array<ComboboxOption<T>>;
|
selectedItems?: Array<ComboboxOption<T>>;
|
||||||
scrollRef: React.RefObject<HTMLDivElement>;
|
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||||
getItemProps: UseComboboxPropGetters<ComboboxOption<T>>['getItemProps'];
|
getItemProps: UseComboboxPropGetters<ComboboxOption<T>>['getItemProps'];
|
||||||
enableAllOption?: boolean;
|
enableAllOption?: boolean;
|
||||||
isMultiSelect?: boolean;
|
isMultiSelect?: boolean;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { autoUpdate, autoPlacement, size, useFloating } from '@floating-ui/react';
|
import { autoUpdate, autoPlacement, size, useFloating } from '@floating-ui/react';
|
||||||
import { useMemo, useRef, useState } from 'react';
|
import { CSSProperties, type RefObject, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { BOUNDARY_ELEMENT_ID } from '../../utils/floating';
|
import { BOUNDARY_ELEMENT_ID } from '../../utils/floating';
|
||||||
import { measureText } from '../../utils/measureText';
|
import { measureText } from '../../utils/measureText';
|
||||||
@@ -21,7 +21,21 @@ const POPOVER_PADDING = 16;
|
|||||||
|
|
||||||
const SCROLL_CONTAINER_PADDING = 8;
|
const SCROLL_CONTAINER_PADDING = 8;
|
||||||
|
|
||||||
export const useComboboxFloat = (items: Array<ComboboxOption<string | number>>, isOpen: boolean) => {
|
interface UseComboboxFloatReturn {
|
||||||
|
inputRef: RefObject<HTMLInputElement | null>;
|
||||||
|
floatingRef: RefObject<HTMLDivElement | null>;
|
||||||
|
scrollRef: RefObject<HTMLDivElement | null>;
|
||||||
|
floatStyles: CSSProperties & {
|
||||||
|
width: number;
|
||||||
|
maxWidth: number;
|
||||||
|
maxHeight: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useComboboxFloat = (
|
||||||
|
items: Array<ComboboxOption<string | number>>,
|
||||||
|
isOpen: boolean
|
||||||
|
): UseComboboxFloatReturn => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const floatingRef = useRef<HTMLDivElement>(null);
|
const floatingRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useStyles2 } from '../../themes/ThemeContext';
|
|||||||
import { List } from '../List/List';
|
import { List } from '../List/List';
|
||||||
|
|
||||||
interface DataLinkSuggestionsProps {
|
interface DataLinkSuggestionsProps {
|
||||||
activeRef?: React.RefObject<HTMLDivElement>;
|
activeRef?: React.RefObject<HTMLDivElement | null>;
|
||||||
suggestions: VariableSuggestion[];
|
suggestions: VariableSuggestion[];
|
||||||
activeIndex: number;
|
activeIndex: number;
|
||||||
onSuggestionSelect: (suggestion: VariableSuggestion) => void;
|
onSuggestionSelect: (suggestion: VariableSuggestion) => void;
|
||||||
@@ -103,7 +103,7 @@ DataLinkSuggestions.displayName = 'DataLinkSuggestions';
|
|||||||
interface DataLinkSuggestionsListProps extends DataLinkSuggestionsProps {
|
interface DataLinkSuggestionsListProps extends DataLinkSuggestionsProps {
|
||||||
label: string;
|
label: string;
|
||||||
activeIndexOffset: number;
|
activeIndexOffset: number;
|
||||||
activeRef?: React.RefObject<HTMLDivElement>;
|
activeRef?: React.RefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataLinkSuggestionsList = React.memo(
|
const DataLinkSuggestionsList = React.memo(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('useListFocus', () => {
|
|||||||
|
|
||||||
const testid = 'test';
|
const testid = 'test';
|
||||||
const getListElement = (
|
const getListElement = (
|
||||||
ref: RefObject<HTMLUListElement>,
|
ref: RefObject<HTMLUListElement | null>,
|
||||||
handleKeys?: (event: KeyboardEvent) => void,
|
handleKeys?: (event: KeyboardEvent) => void,
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
) => (
|
) => (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const CAUGHT_KEYS = ['ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter', 'Tab'];
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export interface UseListFocusProps {
|
export interface UseListFocusProps {
|
||||||
localRef: RefObject<HTMLUListElement>;
|
localRef: RefObject<HTMLUListElement | null>;
|
||||||
options: TimeOption[];
|
options: TimeOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RefObject, useRef } from 'react';
|
import { RefObject, useRef } from 'react';
|
||||||
|
|
||||||
export function useFocus(): [RefObject<HTMLInputElement>, () => void] {
|
export function useFocus(): [RefObject<HTMLInputElement | null>, () => void] {
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
const setFocus = () => {
|
const setFocus = () => {
|
||||||
ref.current && ref.current.focus();
|
ref.current && ref.current.focus();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('useMenuFocus', () => {
|
|||||||
|
|
||||||
const testid = 'test';
|
const testid = 'test';
|
||||||
const getMenuElement = (
|
const getMenuElement = (
|
||||||
ref: RefObject<HTMLDivElement>,
|
ref: RefObject<HTMLDivElement | null>,
|
||||||
handleKeys?: (event: KeyboardEvent) => void,
|
handleKeys?: (event: KeyboardEvent) => void,
|
||||||
handleFocus?: () => void,
|
handleFocus?: () => void,
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const UNFOCUSED = -1;
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export interface UseMenuFocusProps {
|
export interface UseMenuFocusProps {
|
||||||
localRef: RefObject<HTMLDivElement>;
|
localRef: RefObject<HTMLDivElement | null>;
|
||||||
isMenuOpen?: boolean;
|
isMenuOpen?: boolean;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
onOpen?: (focusOnItem: (itemId: number) => void) => void;
|
onOpen?: (focusOnItem: (itemId: number) => void) => void;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { HTMLAttributes, PropsWithChildren, type JSX } from 'react';
|
import { createElement, type HTMLAttributes, type PropsWithChildren, type HTMLElementType, type JSX } from 'react';
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { textUtil } from '@grafana/data';
|
import { textUtil } from '@grafana/data';
|
||||||
|
|
||||||
export interface RenderUserContentAsHTMLProps<T = HTMLSpanElement>
|
export interface RenderUserContentAsHTMLProps<T = HTMLSpanElement>
|
||||||
extends Omit<HTMLAttributes<T>, 'dangerouslySetInnerHTML'> {
|
extends Omit<HTMLAttributes<T>, 'dangerouslySetInnerHTML'> {
|
||||||
component?: keyof React.ReactHTML;
|
component?: HTMLElementType;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ export function RenderUserContentAsHTML<T>({
|
|||||||
content,
|
content,
|
||||||
...rest
|
...rest
|
||||||
}: PropsWithChildren<RenderUserContentAsHTMLProps<T>>): JSX.Element {
|
}: PropsWithChildren<RenderUserContentAsHTMLProps<T>>): JSX.Element {
|
||||||
return React.createElement(component || 'span', {
|
return createElement(component || 'span', {
|
||||||
dangerouslySetInnerHTML: { __html: textUtil.sanitize(content) },
|
dangerouslySetInnerHTML: { __html: textUtil.sanitize(content) },
|
||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface TableCellTooltipProps {
|
|||||||
field: Field;
|
field: Field;
|
||||||
getActions: (field: Field, rowIdx: number) => ActionModel[];
|
getActions: (field: Field, rowIdx: number) => ActionModel[];
|
||||||
getTextColorForBackground: (bgColor: string) => string;
|
getTextColorForBackground: (bgColor: string) => string;
|
||||||
gridRef: RefObject<DataGridHandle>;
|
gridRef: RefObject<DataGridHandle | null>;
|
||||||
height: number;
|
height: number;
|
||||||
placement?: TableCellTooltipPlacement;
|
placement?: TableCellTooltipPlacement;
|
||||||
renderer: TableCellRenderer;
|
renderer: TableCellRenderer;
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ export function useColumnResize(
|
|||||||
return dataGridResizeHandler;
|
return dataGridResizeHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useScrollbarWidth(ref: RefObject<DataGridHandle>, height: number) {
|
export function useScrollbarWidth(ref: RefObject<DataGridHandle | null>, height: number) {
|
||||||
const [scrollbarWidth, setScrollbarWidth] = useState(0);
|
const [scrollbarWidth, setScrollbarWidth] = useState(0);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ interface RowsListProps {
|
|||||||
listHeight: number;
|
listHeight: number;
|
||||||
width: number;
|
width: number;
|
||||||
cellHeight?: TableCellHeight;
|
cellHeight?: TableCellHeight;
|
||||||
listRef: React.RefObject<VariableSizeList>;
|
listRef: React.RefObject<VariableSizeList | null>;
|
||||||
tableState: TableState;
|
tableState: TableState;
|
||||||
tableStyles: TableStyles;
|
tableStyles: TableStyles;
|
||||||
nestedDataField?: Field;
|
nestedDataField?: Field;
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const Table = memo((props: Props) => {
|
|||||||
// `useTableStateReducer`, which is needed to construct options for `useTable` (the hook that returns
|
// `useTableStateReducer`, which is needed to construct options for `useTable` (the hook that returns
|
||||||
// `toggleAllRowsExpanded`), and if we used a variable, that variable would be undefined at the time
|
// `toggleAllRowsExpanded`), and if we used a variable, that variable would be undefined at the time
|
||||||
// we initialize `useTableStateReducer`.
|
// we initialize `useTableStateReducer`.
|
||||||
const toggleAllRowsExpandedRef = useRef<(value?: boolean) => void>();
|
const toggleAllRowsExpandedRef = useRef<(value?: boolean) => void>(null);
|
||||||
|
|
||||||
// Internal react table state reducer
|
// Internal react table state reducer
|
||||||
const stateReducer = useTableStateReducer({
|
const stateReducer = useTableStateReducer({
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import { GrafanaTableState } from './types';
|
|||||||
Select the scrollbar element from the VariableSizeList scope
|
Select the scrollbar element from the VariableSizeList scope
|
||||||
*/
|
*/
|
||||||
export function useFixScrollbarContainer(
|
export function useFixScrollbarContainer(
|
||||||
variableSizeListScrollbarRef: React.RefObject<HTMLDivElement>,
|
variableSizeListScrollbarRef: React.RefObject<HTMLDivElement | null>,
|
||||||
tableDivRef: React.RefObject<HTMLDivElement>
|
tableDivRef: React.RefObject<HTMLDivElement | null>
|
||||||
) {
|
) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (variableSizeListScrollbarRef.current && tableDivRef.current) {
|
if (variableSizeListScrollbarRef.current && tableDivRef.current) {
|
||||||
@@ -43,7 +43,7 @@ export function useFixScrollbarContainer(
|
|||||||
*/
|
*/
|
||||||
export function useResetVariableListSizeCache(
|
export function useResetVariableListSizeCache(
|
||||||
extendedState: GrafanaTableState,
|
extendedState: GrafanaTableState,
|
||||||
listRef: React.RefObject<VariableSizeList>,
|
listRef: React.RefObject<VariableSizeList | null>,
|
||||||
data: DataFrame,
|
data: DataFrame,
|
||||||
hasUniqueId: boolean
|
hasUniqueId: boolean
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ interface EventsCanvasProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords, config }: EventsCanvasProps) {
|
export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords, config }: EventsCanvasProps) {
|
||||||
const plotInstance = useRef<uPlot>();
|
const plotInstance = useRef<uPlot>(null);
|
||||||
// render token required to re-render annotation markers. Rendering lines happens in uPlot and the props do not change
|
// render token required to re-render annotation markers. Rendering lines happens in uPlot and the props do not change
|
||||||
// so we need to force the re-render when the draw hook was performed by uPlot
|
// so we need to force the re-render when the draw hook was performed by uPlot
|
||||||
const [renderToken, setRenderToken] = useState(0);
|
const [renderToken, setRenderToken] = useState(0);
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const TooltipPlugin2 = ({
|
|||||||
|
|
||||||
const [{ plot, isHovering, isPinned, contents, style, dismiss }, setState] = useReducer(mergeState, null, initState);
|
const [{ plot, isHovering, isPinned, contents, style, dismiss }, setState] = useReducer(mergeState, null, initState);
|
||||||
|
|
||||||
const sizeRef = useRef<TooltipContainerSize>();
|
const sizeRef = useRef<TooltipContainerSize>(null);
|
||||||
const styles = useStyles2(getStyles, maxWidth);
|
const styles = useStyles2(getStyles, maxWidth);
|
||||||
|
|
||||||
const renderRef = useRef(render);
|
const renderRef = useRef(render);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export interface GraphNGState {
|
|||||||
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
||||||
static contextType = PanelContextRoot;
|
static contextType = PanelContextRoot;
|
||||||
panelContext: PanelContext = {} as PanelContext;
|
panelContext: PanelContext = {} as PanelContext;
|
||||||
private plotInstance: React.RefObject<uPlot>;
|
private plotInstance: React.RefObject<uPlot | null>;
|
||||||
|
|
||||||
private subscription = new Subscription();
|
private subscription = new Subscription();
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const TooltipPlugin = ({
|
|||||||
renderTooltip,
|
renderTooltip,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: TooltipPluginProps) => {
|
}: TooltipPluginProps) => {
|
||||||
const plotInstance = useRef<uPlot>();
|
const plotInstance = useRef<uPlot>(null);
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function useDelayedSwitch(value: boolean, options: DelayOptions = {}): bo
|
|||||||
const { duration = 250, delay = 250 } = options;
|
const { duration = 250, delay = 250 } = options;
|
||||||
|
|
||||||
const [delayedValue, setDelayedValue] = useState(value);
|
const [delayedValue, setDelayedValue] = useState(value);
|
||||||
const onStartTime = useRef<Date | undefined>();
|
const onStartTime = useRef<Date | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
@@ -34,7 +35,10 @@ export const ForgottenPassword = () => {
|
|||||||
const sendEmail = async (formModel: EmailDTO) => {
|
const sendEmail = async (formModel: EmailDTO) => {
|
||||||
const res = await getBackendSrv().post('/api/user/password/send-reset-email', formModel);
|
const res = await getBackendSrv().post('/api/user/password/send-reset-email', formModel);
|
||||||
if (res) {
|
if (res) {
|
||||||
|
// TODO why?
|
||||||
|
flushSync(() => {
|
||||||
setEmailSent(true);
|
setEmailSent(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const defaultMatchers = {
|
|||||||
* "Time as X" core component, expects ascending x
|
* "Time as X" core component, expects ascending x
|
||||||
*/
|
*/
|
||||||
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
||||||
private plotInstance: React.RefObject<uPlot>;
|
private plotInstance: React.RefObject<uPlot | null>;
|
||||||
|
|
||||||
constructor(props: GraphNGProps) {
|
constructor(props: GraphNGProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act } from '@testing-library/react';
|
||||||
import { comboboxTestSetup } from 'test/helpers/comboboxTestSetup';
|
import { comboboxTestSetup } from 'test/helpers/comboboxTestSetup';
|
||||||
import { getSelectParent, selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
import { getSelectParent, selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||||
import { render, screen, userEvent, waitFor, within } from 'test/test-utils';
|
import { render, screen, userEvent, waitFor, within } from 'test/test-utils';
|
||||||
@@ -29,7 +30,9 @@ const selectComboboxOptionInTest = async (input: HTMLElement, optionOrOptions: s
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
const view = render(<SharedPreferences resourceUri="user" preferenceType="user" />);
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
const view = await act(() => render(<SharedPreferences resourceUri="user" preferenceType="user" />));
|
||||||
const themeSelect = await screen.findByRole('combobox', { name: 'Interface theme' });
|
const themeSelect = await screen.findByRole('combobox', { name: 'Interface theme' });
|
||||||
await waitFor(() => expect(themeSelect).not.toBeDisabled());
|
await waitFor(() => expect(themeSelect).not.toBeDisabled());
|
||||||
return view;
|
return view;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans, t } from '@grafana/i18n';
|
||||||
@@ -25,7 +26,10 @@ export const VerifyEmail = () => {
|
|||||||
getBackendSrv()
|
getBackendSrv()
|
||||||
.post('/api/user/signup', formModel)
|
.post('/api/user/signup', formModel)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
// TODO why?
|
||||||
|
flushSync(() => {
|
||||||
setEmailSent(true);
|
setEmailSent(true);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const msg = err.data?.message || err;
|
const msg = err.data?.message || err;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
import { InitialEntry } from 'history';
|
import { InitialEntry } from 'history';
|
||||||
import { last } from 'lodash';
|
import { last } from 'lodash';
|
||||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||||
@@ -144,8 +145,11 @@ const fillOutForm = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveMuteTiming = async () => {
|
const saveMuteTiming = async () => {
|
||||||
const user = userEvent.setup();
|
// TODO investigate why we need act/fireEvent
|
||||||
await user.click(await screen.findByText(/save time interval/i));
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
const button = await screen.findByText(/save time interval/i);
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(button));
|
||||||
};
|
};
|
||||||
|
|
||||||
setupMswServer();
|
setupMswServer();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { HTMLAttributes } from 'react';
|
|||||||
|
|
||||||
import { Button, IconSize } from '@grafana/ui';
|
import { Button, IconSize } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLButtonElement> {
|
interface Props extends Omit<HTMLAttributes<HTMLButtonElement>, 'onToggle'> {
|
||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
onToggle: (isCollapsed: boolean) => void;
|
onToggle: (isCollapsed: boolean) => void;
|
||||||
// Todo: this should be made compulsory for a11y purposes
|
// Todo: this should be made compulsory for a11y purposes
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||||
import { Props } from 'react-virtualized-auto-sizer';
|
import { Props } from 'react-virtualized-auto-sizer';
|
||||||
import { render, userEvent, waitFor, waitForElementToBeRemoved } from 'test/test-utils';
|
import { render, waitFor, waitForElementToBeRemoved } from 'test/test-utils';
|
||||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||||
|
|
||||||
import { mockExportApi, setupMswServer } from '../../mockApi';
|
import { mockExportApi, setupMswServer } from '../../mockApi';
|
||||||
@@ -72,14 +73,15 @@ describe('GrafanaModifyExport', () => {
|
|||||||
json: 'Json Export Content',
|
json: 'Json Export Content',
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = userEvent.setup();
|
|
||||||
|
|
||||||
renderModifyExport(grafanaRulerRule.grafana_alert.uid);
|
renderModifyExport(grafanaRulerRule.grafana_alert.uid);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => ui.loading.get());
|
await waitForElementToBeRemoved(() => ui.loading.get());
|
||||||
expect(await ui.form.nameInput.find()).toHaveValue('Grafana-rule');
|
expect(await ui.form.nameInput.find()).toHaveValue('Grafana-rule');
|
||||||
|
|
||||||
await user.click(ui.exportButton.get());
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(ui.exportButton.get()));
|
||||||
|
|
||||||
const drawer = await ui.exportDrawer.dialog.find();
|
const drawer = await ui.exportDrawer.dialog.find();
|
||||||
expect(drawer).toBeInTheDocument();
|
expect(drawer).toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
import { render, testWithFeatureToggles, waitFor } from 'test/test-utils';
|
import { render, testWithFeatureToggles, waitFor } from 'test/test-utils';
|
||||||
import { byLabelText, byRole } from 'testing-library-selector';
|
import { byLabelText, byRole } from 'testing-library-selector';
|
||||||
|
|
||||||
@@ -156,7 +157,10 @@ groups:
|
|||||||
await user.click(await ui.dsImport.mimirDsOption.find());
|
await user.click(await ui.dsImport.mimirDsOption.find());
|
||||||
|
|
||||||
// Click the import button
|
// Click the import button
|
||||||
await user.click(ui.importButton.get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(ui.importButton.get()));
|
||||||
|
|
||||||
// Verify confirmation dialog appears
|
// Verify confirmation dialog appears
|
||||||
expect(await ui.confirmationModal.find()).toBeInTheDocument();
|
expect(await ui.confirmationModal.find()).toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||||
import { render, screen } from 'test/test-utils';
|
import { render, screen } from 'test/test-utils';
|
||||||
import { byPlaceholderText, byRole, byTestId } from 'testing-library-selector';
|
import { byPlaceholderText, byRole, byTestId } from 'testing-library-selector';
|
||||||
@@ -65,7 +66,10 @@ describe('new receiver', () => {
|
|||||||
await user.type(email, 'tester@grafana.com');
|
await user.type(email, 'tester@grafana.com');
|
||||||
|
|
||||||
// try to test the contact point
|
// try to test the contact point
|
||||||
await user.click(await ui.testContactPointButton.find());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(async () => fireEvent.click(await ui.testContactPointButton.find()));
|
||||||
|
|
||||||
expect(await ui.testContactPointModal.find()).toBeInTheDocument();
|
expect(await ui.testContactPointModal.find()).toBeInTheDocument();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act } from '@testing-library/react';
|
||||||
import type { JSX } from 'react';
|
import type { JSX } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { render, screen, within } from 'test/test-utils';
|
import { render, screen, within } from 'test/test-utils';
|
||||||
@@ -301,6 +302,10 @@ describe('AnnotationsField', function () {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||||
|
await act(() =>
|
||||||
render(
|
render(
|
||||||
<FormWrapper
|
<FormWrapper
|
||||||
formValues={{
|
formValues={{
|
||||||
@@ -310,6 +315,7 @@ describe('AnnotationsField', function () {
|
|||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await ui.dashboardAnnotation.find()).toBeInTheDocument();
|
expect(await ui.dashboardAnnotation.find()).toBeInTheDocument();
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ describe('RuleViewer', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ruleUid: 'test-rule-uid',
|
ruleUid: 'test-rule-uid',
|
||||||
}),
|
}),
|
||||||
expect.any(Object)
|
undefined
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -500,7 +500,7 @@ describe('RuleViewer', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ruleUid: 'test-rule-uid',
|
ruleUid: 'test-rule-uid',
|
||||||
}),
|
}),
|
||||||
expect.any(Object)
|
undefined
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
import { byRole, byText } from 'testing-library-selector';
|
import { byRole, byText } from 'testing-library-selector';
|
||||||
@@ -58,7 +59,7 @@ describe('Moving a Grafana managed rule', () => {
|
|||||||
|
|
||||||
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
||||||
|
|
||||||
const { user } = render(
|
render(
|
||||||
<MoveRuleTestComponent
|
<MoveRuleTestComponent
|
||||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||||
@@ -66,7 +67,10 @@ describe('Moving a Grafana managed rule', () => {
|
|||||||
rule={ruleToMove}
|
rule={ruleToMove}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await user.click(byRole('button').get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(byRole('button').get()));
|
||||||
|
|
||||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ describe('Moving a Grafana managed rule', () => {
|
|||||||
uid: 'does-not-exist',
|
uid: 'does-not-exist',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { user } = render(
|
render(
|
||||||
<MoveRuleTestComponent
|
<MoveRuleTestComponent
|
||||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||||
targetRuleGroupIdentifier={currentRuleGroupID}
|
targetRuleGroupIdentifier={currentRuleGroupID}
|
||||||
@@ -95,7 +99,10 @@ describe('Moving a Grafana managed rule', () => {
|
|||||||
rule={grafanaRulerRule}
|
rule={grafanaRulerRule}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await user.click(byRole('button').get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(byRole('button').get()));
|
||||||
|
|
||||||
expect(await byText(/error/i).find()).toBeInTheDocument();
|
expect(await byText(/error/i).find()).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -130,7 +137,7 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
draft.grafana_alert.title = 'updated rule title';
|
draft.grafana_alert.title = 'updated rule title';
|
||||||
});
|
});
|
||||||
|
|
||||||
const { user } = render(
|
render(
|
||||||
<MoveRuleTestComponent
|
<MoveRuleTestComponent
|
||||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||||
@@ -138,7 +145,10 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
rule={newRule}
|
rule={newRule}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await user.click(byRole('button').get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(byRole('button').get()));
|
||||||
|
|
||||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -167,7 +177,7 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
|
|
||||||
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
||||||
|
|
||||||
const { user } = render(
|
render(
|
||||||
<MoveRuleTestComponent
|
<MoveRuleTestComponent
|
||||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||||
@@ -175,7 +185,10 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
rule={ruleToMove}
|
rule={ruleToMove}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await user.click(byRole('button').get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(byRole('button').get()));
|
||||||
|
|
||||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -206,7 +219,7 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
|
|
||||||
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
||||||
|
|
||||||
const { user } = render(
|
render(
|
||||||
<MoveRuleTestComponent
|
<MoveRuleTestComponent
|
||||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||||
@@ -214,7 +227,10 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
rule={ruleToMove}
|
rule={ruleToMove}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await user.click(byRole('button').get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(byRole('button').get()));
|
||||||
|
|
||||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -239,7 +255,7 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
draft.grafana_alert.title = 'updated rule title';
|
draft.grafana_alert.title = 'updated rule title';
|
||||||
});
|
});
|
||||||
|
|
||||||
const { user } = render(
|
render(
|
||||||
<MoveRuleTestComponent
|
<MoveRuleTestComponent
|
||||||
currentRuleGroupIdentifier={curentRuleGroupID}
|
currentRuleGroupIdentifier={curentRuleGroupID}
|
||||||
targetRuleGroupIdentifier={curentRuleGroupID}
|
targetRuleGroupIdentifier={curentRuleGroupID}
|
||||||
@@ -247,7 +263,10 @@ describe('Moving a Data source managed rule', () => {
|
|||||||
rule={newRule}
|
rule={newRule}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await user.click(byRole('button').get());
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(byRole('button').get()));
|
||||||
|
|
||||||
expect(await byText(/error/i).find()).toBeInTheDocument();
|
expect(await byText(/error/i).find()).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ describe('pause rule', () => {
|
|||||||
expect(byText(/uninitialized/i).get()).toBeInTheDocument();
|
expect(byText(/uninitialized/i).get()).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(byRole('button').get());
|
await userEvent.click(byRole('button').get());
|
||||||
expect(await byText(/loading/i).find()).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||||
expect(await byText(/result/i).find()).toBeInTheDocument();
|
expect(await byText(/result/i).find()).toBeInTheDocument();
|
||||||
@@ -68,7 +67,6 @@ describe('pause rule', () => {
|
|||||||
expect(await byText(/uninitialized/i).find()).toBeInTheDocument();
|
expect(await byText(/uninitialized/i).find()).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(byRole('button').get());
|
await userEvent.click(byRole('button').get());
|
||||||
expect(await byText(/loading/i).find()).toBeInTheDocument();
|
|
||||||
expect(byText(/success/i).query()).not.toBeInTheDocument();
|
expect(byText(/success/i).query()).not.toBeInTheDocument();
|
||||||
expect(await byText(/error:(.+)oops/i).find()).toBeInTheDocument();
|
expect(await byText(/error:(.+)oops/i).find()).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ export function useAsync<Result, Args extends unknown[] = unknown[]>(
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
result: initialValue,
|
result: initialValue,
|
||||||
});
|
});
|
||||||
const promiseRef = useRef<Promise<Result>>();
|
const promiseRef = useRef<Promise<Result>>(undefined);
|
||||||
const argsRef = useRef<Args>();
|
const argsRef = useRef<Args>(undefined);
|
||||||
|
|
||||||
const methods = useSyncedRef({
|
const methods = useSyncedRef({
|
||||||
execute(...params: Args) {
|
execute(...params: Args) {
|
||||||
|
|||||||
@@ -155,8 +155,8 @@ export function useRuleGroupConsistencyCheck() {
|
|||||||
const { isGroupInSync } = useRuleGroupIsInSync();
|
const { isGroupInSync } = useRuleGroupIsInSync();
|
||||||
const [groupConsistent, setGroupConsistent] = useState<boolean | undefined>();
|
const [groupConsistent, setGroupConsistent] = useState<boolean | undefined>();
|
||||||
|
|
||||||
const apiCheckInterval = useRef<ReturnType<typeof setTimeout> | undefined>();
|
const apiCheckInterval = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||||
const timeoutInterval = useRef<ReturnType<typeof setTimeout> | undefined>();
|
const timeoutInterval = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -245,8 +245,8 @@ export function useRuleGroupConsistencyCheck() {
|
|||||||
export function usePrometheusConsistencyCheck() {
|
export function usePrometheusConsistencyCheck() {
|
||||||
const { matchingPromRuleExists } = useMatchingPromRuleExists();
|
const { matchingPromRuleExists } = useMatchingPromRuleExists();
|
||||||
|
|
||||||
const removalConsistencyInterval = useRef<number | undefined>();
|
const removalConsistencyInterval = useRef<number | undefined>(undefined);
|
||||||
const creationConsistencyInterval = useRef<number | undefined>();
|
const creationConsistencyInterval = useRef<number | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
import { render, screen, waitFor, within } from 'test/test-utils';
|
import { render, screen, waitFor, within } from 'test/test-utils';
|
||||||
import { byRole } from 'testing-library-selector';
|
import { byRole } from 'testing-library-selector';
|
||||||
|
|
||||||
@@ -58,7 +59,10 @@ describe('RuleList - GroupedView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should paginate through groups', async () => {
|
it('should paginate through groups', async () => {
|
||||||
const { user } = render(<GroupedView />);
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||||
|
await act(() => render(<GroupedView />));
|
||||||
|
|
||||||
const mimirSection = await ui.dsSection(/Mimir/).find();
|
const mimirSection = await ui.dsSection(/Mimir/).find();
|
||||||
|
|
||||||
@@ -73,7 +77,10 @@ describe('RuleList - GroupedView', () => {
|
|||||||
expect(firstPageGroups[39]).toHaveTextContent('test-group-40');
|
expect(firstPageGroups[39]).toHaveTextContent('test-group-40');
|
||||||
|
|
||||||
const loadMoreButton = await within(mimirSection).findByRole('button', { name: /Show more/i });
|
const loadMoreButton = await within(mimirSection).findByRole('button', { name: /Show more/i });
|
||||||
await user.click(loadMoreButton);
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(() => fireEvent.click(loadMoreButton));
|
||||||
|
|
||||||
await waitFor(() => expect(loadMoreButton).toBeEnabled());
|
await waitFor(() => expect(loadMoreButton).toBeEnabled());
|
||||||
|
|
||||||
@@ -86,7 +93,10 @@ describe('RuleList - GroupedView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should disable next button when there is no more data', async () => {
|
it('should disable next button when there is no more data', async () => {
|
||||||
const { user } = render(<GroupedView />);
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||||
|
await act(() => render(<GroupedView />));
|
||||||
|
|
||||||
const prometheusSection = await ui.dsSection(/Prometheus/).find();
|
const prometheusSection = await ui.dsSection(/Prometheus/).find();
|
||||||
const promNamespace = await ui.namespace(/test-prometheus-namespace/).find(prometheusSection);
|
const promNamespace = await ui.namespace(/test-prometheus-namespace/).find(prometheusSection);
|
||||||
@@ -96,17 +106,27 @@ describe('RuleList - GroupedView', () => {
|
|||||||
await ui.group('test-group-40').find(promNamespace);
|
await ui.group('test-group-40').find(promNamespace);
|
||||||
|
|
||||||
// fetch page 2
|
// fetch page 2
|
||||||
await user.click(await loadMoreButton.find(prometheusSection));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(async () => fireEvent.click(await loadMoreButton.find(prometheusSection)));
|
||||||
|
// await user.click(await loadMoreButton.find(prometheusSection));
|
||||||
// we should now have all groups 1-80
|
// we should now have all groups 1-80
|
||||||
await ui.group('test-group-80').find(promNamespace);
|
await ui.group('test-group-80').find(promNamespace);
|
||||||
|
|
||||||
// fetch page 3
|
// fetch page 3
|
||||||
await user.click(await loadMoreButton.find(prometheusSection));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(async () => fireEvent.click(await loadMoreButton.find(prometheusSection)));
|
||||||
// we should now have all groups 1-120
|
// we should now have all groups 1-120
|
||||||
await ui.group('test-group-120').find(promNamespace);
|
await ui.group('test-group-120').find(promNamespace);
|
||||||
|
|
||||||
// fetch page 4
|
// fetch page 4
|
||||||
await user.click(await loadMoreButton.find(prometheusSection));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||||
|
await act(async () => fireEvent.click(await loadMoreButton.find(prometheusSection)));
|
||||||
// we should now have all groups 1-130
|
// we should now have all groups 1-130
|
||||||
await ui.group('test-group-130').find(promNamespace);
|
await ui.group('test-group-130').find(promNamespace);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ type Props = {
|
|||||||
|
|
||||||
function LoadMoreHelper({ handleLoad }: Props) {
|
function LoadMoreHelper({ handleLoad }: Props) {
|
||||||
const intersectionRef = useRef<HTMLDivElement>(null);
|
const intersectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
// TODO remove when react-use is fixed
|
||||||
|
// see https://github.com/streamich/react-use/issues/2612
|
||||||
|
// @ts-expect-error
|
||||||
const intersection = useIntersection(intersectionRef, {
|
const intersection = useIntersection(intersectionRef, {
|
||||||
root: null,
|
root: null,
|
||||||
threshold: 1,
|
threshold: 1,
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ interface ListModeProps {
|
|||||||
hasSearches: boolean;
|
hasSearches: boolean;
|
||||||
canSave: boolean;
|
canSave: boolean;
|
||||||
activeAction: ActiveAction;
|
activeAction: ActiveAction;
|
||||||
saveButtonRef: React.RefObject<HTMLButtonElement>;
|
saveButtonRef: React.RefObject<HTMLButtonElement | null>;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
onStartSave: () => void;
|
onStartSave: () => void;
|
||||||
/** Callback to complete save. Throws ValidationError on validation failure. */
|
/** Callback to complete save. Throws ValidationError on validation failure. */
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
query: expect.objectContaining({ queryType: 'defaultAnnotationsQuery', refId: 'initialAnnotationRef' }),
|
query: expect.objectContaining({ queryType: 'defaultAnnotationsQuery', refId: 'initialAnnotationRef' }),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
query: expect.objectContaining({ refId: 'initialAnnotationRef' }),
|
query: expect.objectContaining({ refId: 'initialAnnotationRef' }),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
|||||||
refId: 'A',
|
refId: 'A',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
|||||||
legendFormat: '{{method}} {{endpoint}}',
|
legendFormat: '{{method}} {{endpoint}}',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
|||||||
refId: 'AnnoTarget',
|
refId: 'AnnoTarget',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
|||||||
expr: 'up',
|
expr: 'up',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ function useScopesRow(onApply: () => void) {
|
|||||||
function useGlobalScopesSearch(searchQuery: string, parentId?: string | null) {
|
function useGlobalScopesSearch(searchQuery: string, parentId?: string | null) {
|
||||||
const { selectScope, searchAllNodes, getScopeNodes } = useScopeServicesState();
|
const { selectScope, searchAllNodes, getScopeNodes } = useScopeServicesState();
|
||||||
const [actions, setActions] = useState<CommandPaletteAction[] | undefined>(undefined);
|
const [actions, setActions] = useState<CommandPaletteAction[] | undefined>(undefined);
|
||||||
const searchQueryRef = useRef<string>();
|
const searchQueryRef = useRef<string>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ((!parentId || parentId === 'scopes') && searchQuery && config.featureToggles.scopeSearchAllLevels) {
|
if ((!parentId || parentId === 'scopes') && searchQuery && config.featureToggles.scopeSearchAllLevels) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { render, waitFor, screen, within, Matcher, getByRole } from '@testing-library/react';
|
import { render, waitFor, screen, within, Matcher, getByRole, act, fireEvent } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { merge, uniqueId } from 'lodash';
|
import { merge, uniqueId } from 'lodash';
|
||||||
import { openMenu } from 'react-select-event';
|
import { openMenu } from 'react-select-event';
|
||||||
@@ -300,7 +300,9 @@ describe('CorrelationsPage', () => {
|
|||||||
await userEvent.click(screen.getByText('Regular expression'));
|
await userEvent.click(screen.getByText('Regular expression'));
|
||||||
await userEvent.type(screen.getByLabelText(/expression/i), 'test expression');
|
await userEvent.type(screen.getByLabelText(/expression/i), 'test expression');
|
||||||
|
|
||||||
await userEvent.click(await screen.findByRole('button', { name: /add$/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /add$/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_added');
|
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_added');
|
||||||
@@ -451,7 +453,9 @@ describe('CorrelationsPage', () => {
|
|||||||
await userEvent.clear(screen.getByRole('textbox', { name: /results field/i }));
|
await userEvent.clear(screen.getByRole('textbox', { name: /results field/i }));
|
||||||
await userEvent.type(screen.getByRole('textbox', { name: /results field/i }), 'Line');
|
await userEvent.type(screen.getByRole('textbox', { name: /results field/i }), 'Line');
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: /add$/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /add$/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_added');
|
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_added');
|
||||||
@@ -518,7 +522,9 @@ describe('CorrelationsPage', () => {
|
|||||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /save$/i })));
|
||||||
|
|
||||||
expect(await screen.findByRole('cell', { name: /edited label$/i }, { timeout: 5000 })).toBeInTheDocument();
|
expect(await screen.findByRole('cell', { name: /edited label$/i }, { timeout: 5000 })).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -536,7 +542,9 @@ describe('CorrelationsPage', () => {
|
|||||||
const rowExpanderButton = within(tableRows[0]).getByRole('button', { name: /toggle row expanded/i });
|
const rowExpanderButton = within(tableRows[0]).getByRole('button', { name: /toggle row expanded/i });
|
||||||
await userEvent.click(rowExpanderButton);
|
await userEvent.click(rowExpanderButton);
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /next$/i })));
|
||||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||||
|
|
||||||
// select Logfmt, be sure expression field is disabled
|
// select Logfmt, be sure expression field is disabled
|
||||||
@@ -575,7 +583,9 @@ describe('CorrelationsPage', () => {
|
|||||||
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
||||||
expect(screen.getByText('Please define an expression')).toBeInTheDocument();
|
expect(screen.getByText('Please define an expression')).toBeInTheDocument();
|
||||||
await userEvent.type(screen.getByLabelText(/expression/i), 'test expression');
|
await userEvent.type(screen.getByLabelText(/expression/i), 'test expression');
|
||||||
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /save$/i })));
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_edited');
|
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_edited');
|
||||||
});
|
});
|
||||||
@@ -726,7 +736,9 @@ describe('CorrelationsPage', () => {
|
|||||||
expect(descriptionInput).toBeInTheDocument();
|
expect(descriptionInput).toBeInTheDocument();
|
||||||
expect(descriptionInput).toHaveAttribute('readonly');
|
expect(descriptionInput).toHaveAttribute('readonly');
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /next$/i })));
|
||||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||||
|
|
||||||
// expect the transformation to exist but be read only
|
// expect the transformation to exist but be read only
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ jest.mock('app/features/plugins/extensions/getPluginExtensions', () => ({
|
|||||||
createPluginExtensionsGetter: () => getPluginExtensionsMock,
|
createPluginExtensionsGetter: () => getPluginExtensionsMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function setup({ routeProps }: { routeProps?: Partial<GrafanaRouteComponentProps> } = {}) {
|
async function setup({ routeProps }: { routeProps?: Partial<GrafanaRouteComponentProps> } = {}) {
|
||||||
const context = getGrafanaContextMock();
|
const context = getGrafanaContextMock();
|
||||||
const defaultRouteProps = getRouteComponentProps();
|
const defaultRouteProps = getRouteComponentProps();
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
@@ -66,21 +66,29 @@ function setup({ routeProps }: { routeProps?: Partial<GrafanaRouteComponentProps
|
|||||||
...routeProps,
|
...routeProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderResult = render(
|
// react 19 changed how suspense rendering works
|
||||||
|
// RTL hasn't caught up yet
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
// TODO remove this hack when RTL is updated. probably `render` itself will become async
|
||||||
|
const renderResult = await act(async () =>
|
||||||
|
render(
|
||||||
<TestProvider grafanaContext={context}>
|
<TestProvider grafanaContext={context}>
|
||||||
<LocationServiceProvider service={locationService}>
|
<LocationServiceProvider service={locationService}>
|
||||||
<DashboardScenePage {...props} />
|
<DashboardScenePage {...props} />
|
||||||
</LocationServiceProvider>
|
</LocationServiceProvider>
|
||||||
</TestProvider>
|
</TestProvider>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const rerender = (newProps: Props) => {
|
const rerender = async (newProps: Props) => {
|
||||||
|
await act(async () =>
|
||||||
renderResult.rerender(
|
renderResult.rerender(
|
||||||
<TestProvider grafanaContext={context}>
|
<TestProvider grafanaContext={context}>
|
||||||
<LocationServiceProvider service={locationService}>
|
<LocationServiceProvider service={locationService}>
|
||||||
<DashboardScenePage {...newProps} />
|
<DashboardScenePage {...newProps} />
|
||||||
</LocationServiceProvider>
|
</LocationServiceProvider>
|
||||||
</TestProvider>
|
</TestProvider>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,6 +160,8 @@ describe('DashboardScenePage', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
locationService.push('/d/my-dash-uid');
|
locationService.push('/d/my-dash-uid');
|
||||||
getDashboardScenePageStateManager().clearDashboardCache();
|
getDashboardScenePageStateManager().clearDashboardCache();
|
||||||
|
getDashboardScenePageStateManager().clearSceneCache();
|
||||||
|
getDashboardScenePageStateManager().clearState();
|
||||||
loadDashboardMock.mockClear();
|
loadDashboardMock.mockClear();
|
||||||
loadDashboardMock.mockResolvedValue({ dashboard: simpleDashboard, meta: { slug: '123' } });
|
loadDashboardMock.mockResolvedValue({ dashboard: simpleDashboard, meta: { slug: '123' } });
|
||||||
// hacky way because mocking autosizer does not work
|
// hacky way because mocking autosizer does not work
|
||||||
@@ -163,7 +173,7 @@ describe('DashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Can render dashboard', async () => {
|
it('Can render dashboard', async () => {
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitForDashboardToRender();
|
await waitForDashboardToRender();
|
||||||
|
|
||||||
@@ -175,7 +185,7 @@ describe('DashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('routeReloadCounter should trigger reload', async () => {
|
it('routeReloadCounter should trigger reload', async () => {
|
||||||
const { rerender, props } = setup();
|
const { rerender, props } = await setup();
|
||||||
|
|
||||||
await waitForDashboardToRender();
|
await waitForDashboardToRender();
|
||||||
|
|
||||||
@@ -190,13 +200,13 @@ describe('DashboardScenePage', () => {
|
|||||||
|
|
||||||
props.location.state = { routeReloadCounter: 1 };
|
props.location.state = { routeReloadCounter: 1 };
|
||||||
|
|
||||||
rerender(props);
|
await rerender(props);
|
||||||
|
|
||||||
expect(await screen.findByTitle('Updated title')).toBeInTheDocument();
|
expect(await screen.findByTitle('Updated title')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can inspect panel', async () => {
|
it('Can inspect panel', async () => {
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitForDashboardToRender();
|
await waitForDashboardToRender();
|
||||||
|
|
||||||
@@ -218,7 +228,7 @@ describe('DashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Can view panel in fullscreen', async () => {
|
it('Can view panel in fullscreen', async () => {
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitForDashboardToRender();
|
await waitForDashboardToRender();
|
||||||
|
|
||||||
@@ -238,7 +248,7 @@ describe('DashboardScenePage', () => {
|
|||||||
interval: {} as SystemDateFormatsState['interval'],
|
interval: {} as SystemDateFormatsState['interval'],
|
||||||
useBrowserLocale: true,
|
useBrowserLocale: true,
|
||||||
});
|
});
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitForDashboardToRenderWithTimeRange({
|
await waitForDashboardToRenderWithTimeRange({
|
||||||
from: '03/11/2025, 02:09:37 AM',
|
from: '03/11/2025, 02:09:37 AM',
|
||||||
@@ -257,7 +267,7 @@ describe('DashboardScenePage', () => {
|
|||||||
interval: {} as SystemDateFormatsState['interval'],
|
interval: {} as SystemDateFormatsState['interval'],
|
||||||
useBrowserLocale: true,
|
useBrowserLocale: true,
|
||||||
});
|
});
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitForDashboardToRenderWithTimeRange({
|
await waitForDashboardToRenderWithTimeRange({
|
||||||
from: '11.03.2025, 02:09:37',
|
from: '11.03.2025, 02:09:37',
|
||||||
@@ -269,17 +279,17 @@ describe('DashboardScenePage', () => {
|
|||||||
describe('empty state', () => {
|
describe('empty state', () => {
|
||||||
it('Shows empty state when dashboard is empty', async () => {
|
it('Shows empty state when dashboard is empty', async () => {
|
||||||
loadDashboardMock.mockResolvedValue({ dashboard: { uid: 'my-dash-uid', panels: [] }, meta: {} });
|
loadDashboardMock.mockResolvedValue({ dashboard: { uid: 'my-dash-uid', panels: [] }, meta: {} });
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument();
|
expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows and hides empty state when panels are added and removed', async () => {
|
it('shows and hides empty state when panels are added and removed', async () => {
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitForDashboardToRender();
|
await waitForDashboardToRender();
|
||||||
|
|
||||||
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
expect(screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||||
|
|
||||||
// Hacking a bit, accessing private cache property to get access to the underlying DashboardScene object
|
// Hacking a bit, accessing private cache property to get access to the underlying DashboardScene object
|
||||||
const dashboardScenesCache = getDashboardScenePageStateManager().getCache();
|
const dashboardScenesCache = getDashboardScenePageStateManager().getCache();
|
||||||
@@ -289,7 +299,7 @@ describe('DashboardScenePage', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
dashboard.removePanel(panels[0]);
|
dashboard.removePanel(panels[0]);
|
||||||
});
|
});
|
||||||
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
expect(screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
dashboard.removePanel(panels[1]);
|
dashboard.removePanel(panels[1]);
|
||||||
@@ -301,14 +311,14 @@ describe('DashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(await screen.findByTitle('Panel Added')).toBeInTheDocument();
|
expect(await screen.findByTitle('Panel Added')).toBeInTheDocument();
|
||||||
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
expect(screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('home page', () => {
|
describe('home page', () => {
|
||||||
it('should render the dashboard when the route is home', async () => {
|
it('should render the dashboard when the route is home', async () => {
|
||||||
(useParams as jest.Mock).mockReturnValue({});
|
(useParams as jest.Mock).mockReturnValue({});
|
||||||
setup({
|
await setup({
|
||||||
routeProps: {
|
routeProps: {
|
||||||
route: {
|
route: {
|
||||||
...getRouteComponentProps().route,
|
...getRouteComponentProps().route,
|
||||||
@@ -331,7 +341,7 @@ describe('DashboardScenePage', () => {
|
|||||||
loadDashboardMock.mockClear();
|
loadDashboardMock.mockClear();
|
||||||
loadDashboardMock.mockResolvedValue({ dashboard: { uid: 'my-dash-uid', panels: [] }, meta: {} });
|
loadDashboardMock.mockResolvedValue({ dashboard: { uid: 'my-dash-uid', panels: [] }, meta: {} });
|
||||||
|
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
await waitFor(() => expect(screen.queryByText('Refresh')).toBeInTheDocument());
|
await waitFor(() => expect(screen.queryByText('Refresh')).toBeInTheDocument());
|
||||||
await waitFor(() => expect(screen.queryByText('Last 6 hours')).toBeInTheDocument());
|
await waitFor(() => expect(screen.queryByText('Last 6 hours')).toBeInTheDocument());
|
||||||
@@ -363,7 +373,7 @@ describe('DashboardScenePage', () => {
|
|||||||
isHandled: true,
|
isHandled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
expect(await screen.findByTestId(selectors.components.EntityNotFound.container)).toBeInTheDocument();
|
expect(await screen.findByTestId(selectors.components.EntityNotFound.container)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -387,7 +397,7 @@ describe('DashboardScenePage', () => {
|
|||||||
isHandled: true,
|
isHandled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
expect(await screen.findByTestId('dashboard-page-error')).toBeInTheDocument();
|
expect(await screen.findByTestId('dashboard-page-error')).toBeInTheDocument();
|
||||||
expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Internal server error');
|
expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Internal server error');
|
||||||
@@ -396,7 +406,7 @@ describe('DashboardScenePage', () => {
|
|||||||
it('should render error alert for runtime errors', async () => {
|
it('should render error alert for runtime errors', async () => {
|
||||||
setupLoadDashboardRuntimeErrorMock();
|
setupLoadDashboardRuntimeErrorMock();
|
||||||
|
|
||||||
setup();
|
await setup();
|
||||||
|
|
||||||
expect(await screen.findByTestId('dashboard-page-error')).toBeInTheDocument();
|
expect(await screen.findByTestId('dashboard-page-error')).toBeInTheDocument();
|
||||||
expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Runtime error');
|
expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Runtime error');
|
||||||
@@ -411,7 +421,7 @@ describe('DashboardScenePage', () => {
|
|||||||
const manager = getDashboardScenePageStateManager();
|
const manager = getDashboardScenePageStateManager();
|
||||||
manager.setActiveManager('v2');
|
manager.setActiveManager('v2');
|
||||||
|
|
||||||
const { unmount } = setup();
|
const { unmount } = await setup();
|
||||||
|
|
||||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2);
|
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2);
|
||||||
unmount();
|
unmount();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { screen, waitForElementToBeRemoved } from '@testing-library/react';
|
import { act, screen } from '@testing-library/react';
|
||||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
@@ -26,7 +26,7 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function setup(token = 'an-access-token') {
|
async function setup(token = 'an-access-token') {
|
||||||
const pubdashProps: PublicDashboardSceneProps = {
|
const pubdashProps: PublicDashboardSceneProps = {
|
||||||
...getRouteComponentProps({
|
...getRouteComponentProps({
|
||||||
route: {
|
route: {
|
||||||
@@ -37,11 +37,19 @@ function setup(token = 'an-access-token') {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return render(
|
// TODO investigate why act is needed here
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
return await act(() =>
|
||||||
|
render(
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/public-dashboards/:accessToken" element={<PublicDashboardScenePage {...pubdashProps} />} />
|
<Route
|
||||||
|
path="/public-dashboards/:accessToken"
|
||||||
|
element={<PublicDashboardScenePage {...pubdashProps} />}
|
||||||
|
key={token}
|
||||||
|
/>
|
||||||
</Routes>,
|
</Routes>,
|
||||||
{ historyOptions: { initialEntries: [`/public-dashboards/${token}`] } }
|
{ historyOptions: { initialEntries: [`/public-dashboards/${token}`] } }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +123,6 @@ const publicDashboardSceneSelector = e2eSelectors.pages.PublicDashboardScene;
|
|||||||
|
|
||||||
describe('PublicDashboardScenePage', () => {
|
describe('PublicDashboardScenePage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config.publicDashboardAccessToken = 'an-access-token';
|
|
||||||
getDashboardScenePageStateManager().clearDashboardCache();
|
getDashboardScenePageStateManager().clearDashboardCache();
|
||||||
setupLoadDashboardMock({ dashboard: simpleDashboard, meta: {} });
|
setupLoadDashboardMock({ dashboard: simpleDashboard, meta: {} });
|
||||||
|
|
||||||
@@ -125,7 +132,9 @@ describe('PublicDashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('can render public dashboard', async () => {
|
it('can render public dashboard', async () => {
|
||||||
setup();
|
const accessToken = 'an-access-token';
|
||||||
|
config.publicDashboardAccessToken = accessToken;
|
||||||
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForDashboardGridToRender();
|
await waitForDashboardGridToRender();
|
||||||
|
|
||||||
@@ -139,7 +148,9 @@ describe('PublicDashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cannot see menu panel', async () => {
|
it('cannot see menu panel', async () => {
|
||||||
setup();
|
const accessToken = 'cannot-see-menu-panel';
|
||||||
|
config.publicDashboardAccessToken = accessToken;
|
||||||
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForDashboardGridToRender();
|
await waitForDashboardGridToRender();
|
||||||
|
|
||||||
@@ -148,7 +159,9 @@ describe('PublicDashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows time controls when it is not hidden', async () => {
|
it('shows time controls when it is not hidden', async () => {
|
||||||
setup();
|
const accessToken = 'shows-time-controls';
|
||||||
|
config.publicDashboardAccessToken = accessToken;
|
||||||
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForDashboardGridToRender();
|
await waitForDashboardGridToRender();
|
||||||
|
|
||||||
@@ -158,7 +171,9 @@ describe('PublicDashboardScenePage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not render paused or deleted screen', async () => {
|
it('does not render paused or deleted screen', async () => {
|
||||||
setup();
|
const accessToken = 'does-not-render-paused-or-deleted-screen';
|
||||||
|
config.publicDashboardAccessToken = accessToken;
|
||||||
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForDashboardGridToRender();
|
await waitForDashboardGridToRender();
|
||||||
|
|
||||||
@@ -172,7 +187,7 @@ describe('PublicDashboardScenePage', () => {
|
|||||||
dashboard: { ...simpleDashboard, timepicker: { hidden: true } },
|
dashboard: { ...simpleDashboard, timepicker: { hidden: true } },
|
||||||
meta: {},
|
meta: {},
|
||||||
});
|
});
|
||||||
setup(accessToken);
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForDashboardGridToRender();
|
await waitForDashboardGridToRender();
|
||||||
|
|
||||||
@@ -207,9 +222,7 @@ describe('given unavailable public dashboard', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
setup(accessToken);
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(screen.getByTestId(publicDashboardSceneSelector.loadingPage));
|
|
||||||
|
|
||||||
expect(screen.queryByTestId(publicDashboardSceneSelector.page)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(publicDashboardSceneSelector.page)).not.toBeInTheDocument();
|
||||||
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
|
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
|
||||||
@@ -239,9 +252,7 @@ describe('given unavailable public dashboard', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
setup(accessToken);
|
await setup(accessToken);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(screen.getByTestId(publicDashboardSceneSelector.loadingPage));
|
|
||||||
|
|
||||||
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
|||||||
|
|
||||||
const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({});
|
const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({});
|
||||||
|
|
||||||
const validationTimeoutRef = useRef<NodeJS.Timeout>();
|
const validationTimeoutRef = useRef<NodeJS.Timeout>(null);
|
||||||
|
|
||||||
// Validate title on form mount to catch invalid default values
|
// Validate title on form mount to catch invalid default values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -59,14 +59,18 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
|||||||
// Cleanup timeout on unmount
|
// Cleanup timeout on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
if (validationTimeoutRef.current) {
|
||||||
clearTimeout(validationTimeoutRef.current);
|
clearTimeout(validationTimeoutRef.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleTitleChange = useCallback(
|
const handleTitleChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setValue('title', e.target.value, { shouldDirty: true });
|
setValue('title', e.target.value, { shouldDirty: true });
|
||||||
|
if (validationTimeoutRef.current) {
|
||||||
clearTimeout(validationTimeoutRef.current);
|
clearTimeout(validationTimeoutRef.current);
|
||||||
|
}
|
||||||
validationTimeoutRef.current = setTimeout(() => {
|
validationTimeoutRef.current = setTimeout(() => {
|
||||||
trigger('title');
|
trigger('title');
|
||||||
}, 400);
|
}, 400);
|
||||||
@@ -75,7 +79,9 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onSave = async (overwrite: boolean) => {
|
const onSave = async (overwrite: boolean) => {
|
||||||
|
if (validationTimeoutRef.current) {
|
||||||
clearTimeout(validationTimeoutRef.current);
|
clearTimeout(validationTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
const isTitleValid = await trigger('title');
|
const isTitleValid = await trigger('title');
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { DashboardGridItem, RepeatDirection } from './DashboardGridItem';
|
|||||||
interface PanelWrapperProps {
|
interface PanelWrapperProps {
|
||||||
panel: VizPanel;
|
panel: VizPanel;
|
||||||
isLazy: boolean;
|
isLazy: boolean;
|
||||||
containerRef?: RefObject<HTMLDivElement>;
|
containerRef?: RefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PanelWrapper({ panel, isLazy, containerRef }: PanelWrapperProps) {
|
function PanelWrapper({ panel, isLazy, containerRef }: PanelWrapperProps) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
|
|||||||
* Will scroll element into view. If element is not connected yet, it will try to expand rows
|
* Will scroll element into view. If element is not connected yet, it will try to expand rows
|
||||||
* and switch tabs to make it visible.
|
* and switch tabs to make it visible.
|
||||||
*/
|
*/
|
||||||
export function scrollCanvasElementIntoView(sceneObject: SceneObject, ref: React.RefObject<HTMLElement>) {
|
export function scrollCanvasElementIntoView(sceneObject: SceneObject, ref: React.RefObject<HTMLElement | null>) {
|
||||||
if (ref.current?.isConnected) {
|
if (ref.current?.isConnected) {
|
||||||
scrollIntoView(ref.current);
|
scrollIntoView(ref.current);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ global.ResizeObserver = jest.fn().mockImplementation((callback) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to assign a mock div to a ref
|
// Helper function to assign a mock div to a ref
|
||||||
function assignMockDivToRef(ref: React.RefObject<HTMLDivElement>, mockDiv: HTMLDivElement) {
|
function assignMockDivToRef(ref: React.RefObject<HTMLDivElement | null>, mockDiv: HTMLDivElement) {
|
||||||
// Use type assertion to bypass readonly restriction in tests
|
// Use type assertion to bypass readonly restriction in tests
|
||||||
(ref as { current: HTMLDivElement | null }).current = mockDiv;
|
(ref as { current: HTMLDivElement | null }).current = mockDiv;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import grafanaTextLogoDarkSvg from 'img/grafana_text_logo_dark.svg';
|
|||||||
import grafanaTextLogoLightSvg from 'img/grafana_text_logo_light.svg';
|
import grafanaTextLogoLightSvg from 'img/grafana_text_logo_light.svg';
|
||||||
|
|
||||||
interface SoloPanelPageLogoProps {
|
interface SoloPanelPageLogoProps {
|
||||||
containerRef: React.RefObject<HTMLDivElement>;
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
isHovered: boolean;
|
isHovered: boolean;
|
||||||
hideLogo?: UrlQueryValue;
|
hideLogo?: UrlQueryValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { screen, waitFor, within } from '@testing-library/react';
|
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
|
|
||||||
@@ -154,7 +154,9 @@ describe('VersionSettings', () => {
|
|||||||
expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(VERSIONS_FETCH_LIMIT);
|
expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(VERSIONS_FETCH_LIMIT);
|
||||||
|
|
||||||
const showMoreButton = screen.getByRole('button', { name: /show more versions/i });
|
const showMoreButton = screen.getByRole('button', { name: /show more versions/i });
|
||||||
await user.click(showMoreButton);
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
act(() => fireEvent.click(showMoreButton));
|
||||||
|
|
||||||
expect(historySrv.getHistoryList).toBeCalledTimes(2);
|
expect(historySrv.getHistoryList).toBeCalledTimes(2);
|
||||||
expect(screen.getByText(/Fetching more entries/i)).toBeInTheDocument();
|
expect(screen.getByText(/Fetching more entries/i)).toBeInTheDocument();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const usePanelLatestData = (
|
|||||||
options: GetDataOptions,
|
options: GetDataOptions,
|
||||||
checkSchema?: boolean
|
checkSchema?: boolean
|
||||||
): UsePanelLatestData => {
|
): UsePanelLatestData => {
|
||||||
const querySubscription = useRef<Unsubscribable>();
|
const querySubscription = useRef<Unsubscribable>(null);
|
||||||
const [latestData, setLatestData] = useState<PanelData>();
|
const [latestData, setLatestData] = useState<PanelData>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ interface State {
|
|||||||
|
|
||||||
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
|
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
|
||||||
subscription?: Unsubscribable;
|
subscription?: Unsubscribable;
|
||||||
ref: RefObject<HTMLDivElement>;
|
ref: RefObject<HTMLDivElement | null>;
|
||||||
|
|
||||||
constructor(props: TransformationsEditorProps) {
|
constructor(props: TransformationsEditorProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { act, screen, waitFor } from '@testing-library/react';
|
||||||
import { Routes, Route } from 'react-router-dom-v5-compat';
|
import { Routes, Route } from 'react-router-dom-v5-compat';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
|
|
||||||
@@ -62,7 +62,9 @@ describe('PublicDashboardPageProxy', () => {
|
|||||||
describe('when scene feature enabled', () => {
|
describe('when scene feature enabled', () => {
|
||||||
it('should render PublicDashboardScenePage if publicDashboardsScene is enabled', async () => {
|
it('should render PublicDashboardScenePage if publicDashboardsScene is enabled', async () => {
|
||||||
config.featureToggles.publicDashboardsScene = true;
|
config.featureToggles.publicDashboardsScene = true;
|
||||||
setup({});
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => setup({}));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByTestId(PublicDashboardScene.page)).toBeInTheDocument();
|
expect(screen.queryByTestId(PublicDashboardScene.page)).toBeInTheDocument();
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function useDatasource(dataSource: string | DataSourceRef | DataSourceIns
|
|||||||
|
|
||||||
export interface KeybaordNavigatableListProps {
|
export interface KeybaordNavigatableListProps {
|
||||||
keyboardEvents?: Observable<React.KeyboardEvent>;
|
keyboardEvents?: Observable<React.KeyboardEvent>;
|
||||||
containerRef: React.RefObject<HTMLElement>;
|
containerRef: React.RefObject<HTMLElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ThresholdsEditor extends PureComponent<Props, State> {
|
export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||||
private latestThresholdInputRef: React.RefObject<HTMLInputElement>;
|
private latestThresholdInputRef: React.RefObject<HTMLInputElement | null>;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
);
|
);
|
||||||
const styles = useStyles2(getStyles, contentOutlineExpanded);
|
const styles = useStyles2(getStyles, contentOutlineExpanded);
|
||||||
const scrollerRef = useRef(scroller || null);
|
const scrollerRef = useRef(scroller || null);
|
||||||
|
// TODO remove when react-use is fixed
|
||||||
|
// see https://github.com/streamich/react-use/issues/2612
|
||||||
|
// @ts-expect-error
|
||||||
const { y: verticalScroll } = useScroll(scrollerRef);
|
const { y: verticalScroll } = useScroll(scrollerRef);
|
||||||
const { outlineItems } = useContentOutlineContext() ?? { outlineItems: [] };
|
const { outlineItems } = useContentOutlineContext() ?? { outlineItems: [] };
|
||||||
const [activeSectionId, setActiveSectionId] = useState(outlineItems[0]?.id);
|
const [activeSectionId, setActiveSectionId] = useState(outlineItems[0]?.id);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const ExplorePaneContainer = connector(ExplorePaneContainerUnconnected);
|
|||||||
|
|
||||||
function useStopQueries(exploreId: string) {
|
function useStopQueries(exploreId: string) {
|
||||||
const paneSelector = useMemo(() => getExploreItemSelector(exploreId), [exploreId]);
|
const paneSelector = useMemo(() => getExploreItemSelector(exploreId), [exploreId]);
|
||||||
const paneRef = useRef<ReturnType<typeof paneSelector>>();
|
const paneRef = useRef<ReturnType<typeof paneSelector>>(null);
|
||||||
paneRef.current = useSelector(paneSelector);
|
paneRef.current = useSelector(paneSelector);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export type QueryLibraryContextType = {
|
|||||||
onUpdateSuccess?: () => void,
|
onUpdateSuccess?: () => void,
|
||||||
onSelectQuery?: (query: DataQuery) => void,
|
onSelectQuery?: (query: DataQuery) => void,
|
||||||
datasourceFilters?: string[],
|
datasourceFilters?: string[],
|
||||||
parentRef?: React.RefObject<HTMLDivElement>
|
parentRef?: React.RefObject<HTMLDivElement | null>
|
||||||
) => ReactNode;
|
) => ReactNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ type Props = {
|
|||||||
scrollElementClass?: string;
|
scrollElementClass?: string;
|
||||||
traceProp: Trace;
|
traceProp: Trace;
|
||||||
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
|
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
|
||||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
topOfViewRef?: RefObject<HTMLDivElement | null>;
|
||||||
createSpanLink?: SpanLinkFunc;
|
createSpanLink?: SpanLinkFunc;
|
||||||
focusedSpanId?: string;
|
focusedSpanId?: string;
|
||||||
createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel<Field>;
|
createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel<Field>;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ const timeRange = {
|
|||||||
to: new Date(1000),
|
to: new Date(1000),
|
||||||
} as unknown as TimeRange;
|
} as unknown as TimeRange;
|
||||||
|
|
||||||
function getContent(result: React.ReactElement) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function getContent(result: React.ReactElement<any>) {
|
||||||
return result.props.children.props.children[0];
|
return result.props.children.props.children[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -561,7 +561,7 @@ const CardsContainer = ({
|
|||||||
mainContainerRef,
|
mainContainerRef,
|
||||||
}: {
|
}: {
|
||||||
listOfContentCards: React.ReactNode[];
|
listOfContentCards: React.ReactNode[];
|
||||||
mainContainerRef?: React.RefObject<HTMLDivElement>;
|
mainContainerRef?: React.RefObject<HTMLDivElement | null>;
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ type TVirtualizedTraceViewOwnProps = {
|
|||||||
focusedSpanIdForSearch: string;
|
focusedSpanIdForSearch: string;
|
||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
topOfViewRef?: RefObject<HTMLDivElement | null>;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
datasourceUid: string;
|
datasourceUid: string;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export type TProps = {
|
|||||||
focusedSpanIdForSearch: string;
|
focusedSpanIdForSearch: string;
|
||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
topOfViewRef?: RefObject<HTMLDivElement | null>;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
criticalPath: CriticalPathSection[];
|
criticalPath: CriticalPathSection[];
|
||||||
traceFlameGraphs: TraceFlameGraphs;
|
traceFlameGraphs: TraceFlameGraphs;
|
||||||
@@ -197,8 +197,10 @@ export class UnthemedTraceTimelineViewer extends PureComponent<TProps, State> {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.TraceTimelineViewer}
|
className={styles.TraceTimelineViewer}
|
||||||
ref={(ref: HTMLDivElement | null) => {
|
ref={(ref) => {
|
||||||
ref && this.setState({ height: ref.getBoundingClientRect().height });
|
if (ref) {
|
||||||
|
this.setState({ height: ref.getBoundingClientRect().height });
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TimelineHeaderRow
|
<TimelineHeaderRow
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { screen, waitFor, within } from '@testing-library/react';
|
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
|
|
||||||
@@ -103,7 +103,9 @@ describe('SignupInvitedPage', () => {
|
|||||||
get: { email: '', invitedBy: '', name: '', username: '', orgName: '' },
|
get: { email: '', invitedBy: '', name: '', username: '', orgName: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: /sign up/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /sign up/i })));
|
||||||
|
|
||||||
await waitFor(() => expect(screen.getByText(/email is required/i)).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText(/email is required/i)).toBeInTheDocument());
|
||||||
expect(screen.getByText(/username is required/i)).toBeInTheDocument();
|
expect(screen.getByText(/username is required/i)).toBeInTheDocument();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const LibraryPanelsView = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
const asyncDispatch = useMemo(() => asyncDispatcher(dispatch), [dispatch]);
|
const asyncDispatch = useMemo(() => asyncDispatcher(dispatch), [dispatch]);
|
||||||
const abortControllerRef = useRef<AbortController>();
|
const abortControllerRef = useRef<AbortController>(null);
|
||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface Props {}
|
|||||||
|
|
||||||
export const LiveConnectionWarning = memo(function LiveConnectionWarning() {
|
export const LiveConnectionWarning = memo(function LiveConnectionWarning() {
|
||||||
const [show, setShow] = useState<boolean | undefined>(undefined);
|
const [show, setShow] = useState<boolean | undefined>(undefined);
|
||||||
const subscriptionRef = useRef<Unsubscribable>();
|
const subscriptionRef = useRef<Unsubscribable>(null);
|
||||||
const styles = useStyles2(getStyle);
|
const styles = useStyles2(getStyle);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { isObservable, lastValueFrom } from 'rxjs';
|
import { isObservable, lastValueFrom } from 'rxjs';
|
||||||
|
|
||||||
import { DataFrame, DataQueryRequest, DataSourceApi, GrafanaTheme2, TimeRange } from '@grafana/data';
|
import { DataFrame, DataQueryRequest, DataSourceApi, GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||||
@@ -31,7 +32,10 @@ export const LogLineDetailsTrace = ({ timeRange, timeZone, traceRef }: Props) =>
|
|||||||
.get(traceRef.dsUID)
|
.get(traceRef.dsUID)
|
||||||
.then((dataSource) => {
|
.then((dataSource) => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
|
// TODO why?
|
||||||
|
flushSync(() => {
|
||||||
setDataSource(dataSource);
|
setDataSource(dataSource);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setDataFrames(null);
|
setDataFrames(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,7 @@ export function VisualizationSuggestionCard({
|
|||||||
|
|
||||||
content = (
|
content = (
|
||||||
<button {...commonButtonProps}>
|
<button {...commonButtonProps}>
|
||||||
{/* to use inert in React 18, we have to do this hacky object spread thing. https://stackoverflow.com/questions/72720469/error-when-using-inert-attribute-with-typescript */}
|
<div style={innerStyles} className={cx(styles.renderContainer, isSelected && styles.selectedSuggestion)} inert>
|
||||||
<div
|
|
||||||
style={innerStyles}
|
|
||||||
className={cx(styles.renderContainer, isSelected && styles.selectedSuggestion)}
|
|
||||||
{...{ inert: '' }}
|
|
||||||
>
|
|
||||||
<PanelRenderer
|
<PanelRenderer
|
||||||
title=""
|
title=""
|
||||||
data={data}
|
data={data}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { QueryStatus } from '@reduxjs/toolkit/query';
|
import { QueryStatus } from '@reduxjs/toolkit/query';
|
||||||
import { screen, waitFor } from '@testing-library/react';
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
import { UserEvent } from '@testing-library/user-event';
|
import { UserEvent } from '@testing-library/user-event';
|
||||||
import type { JSX } from 'react';
|
import { act, type JSX } from 'react';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -245,7 +245,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
||||||
@@ -253,7 +255,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
|
|
||||||
expect(mockUseCreateOrUpdateRepository).toHaveBeenCalled();
|
expect(mockUseCreateOrUpdateRepository).toHaveBeenCalled();
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Synchronize with external storage/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Synchronize with external storage/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('heading', { name: /3\. Synchronize with external storage/i })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: /3\. Synchronize with external storage/i })).toBeInTheDocument();
|
||||||
@@ -289,7 +293,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
||||||
@@ -347,7 +353,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('Branch "invalid-branch" not found')).toBeInTheDocument();
|
expect(screen.getByText('Branch "invalid-branch" not found')).toBeInTheDocument();
|
||||||
@@ -381,7 +389,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||||
@@ -422,7 +432,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||||
@@ -450,13 +462,17 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Previous/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Previous/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument();
|
||||||
@@ -493,7 +509,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: /Submitting.../i })).toBeDisabled();
|
expect(screen.getByRole('button', { name: /Submitting.../i })).toBeDisabled();
|
||||||
});
|
});
|
||||||
@@ -530,7 +548,9 @@ describe('ProvisioningWizard', () => {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
// TODO investigate why we need act/fireEvent
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(async () => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByRole('button', { name: /Synchronize with external storage/i })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: /Synchronize with external storage/i })).toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
@@ -43,7 +44,10 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions
|
|||||||
const workflow = watch('workflow');
|
const workflow = watch('workflow');
|
||||||
|
|
||||||
const handleSubmitForm = async (data: BulkActionFormData) => {
|
const handleSubmitForm = async (data: BulkActionFormData) => {
|
||||||
|
// TODO why?
|
||||||
|
flushSync(() => {
|
||||||
setHasSubmitted(true);
|
setHasSubmitted(true);
|
||||||
|
});
|
||||||
|
|
||||||
const resources = collectSelectedItems(selectedItems);
|
const resources = collectSelectedItems(selectedItems);
|
||||||
|
|
||||||
|
|||||||
@@ -613,7 +613,7 @@ function SavedQueryButtons(props: {
|
|||||||
onUpdateSuccess?: () => void;
|
onUpdateSuccess?: () => void;
|
||||||
onSelectQuery: (query: DataQuery) => void;
|
onSelectQuery: (query: DataQuery) => void;
|
||||||
datasourceFilters: string[];
|
datasourceFilters: string[];
|
||||||
parentRef: React.RefObject<HTMLDivElement>;
|
parentRef: React.RefObject<HTMLDivElement | null>;
|
||||||
}) {
|
}) {
|
||||||
const { renderSavedQueryButtons } = useQueryLibraryContext();
|
const { renderSavedQueryButtons } = useQueryLibraryContext();
|
||||||
return renderSavedQueryButtons(
|
return renderSavedQueryButtons(
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function useSearchKeyboardNavigation(
|
|||||||
): ItemSelection {
|
): ItemSelection {
|
||||||
const highlightIndexRef = useRef<ItemSelection>({ x: 0, y: -1 });
|
const highlightIndexRef = useRef<ItemSelection>({ x: 0, y: -1 });
|
||||||
const [highlightIndex, setHighlightIndex] = useState<ItemSelection>({ x: 0, y: -1 });
|
const [highlightIndex, setHighlightIndex] = useState<ItemSelection>({ x: 0, y: -1 });
|
||||||
const urlsRef = useRef<Field>();
|
const urlsRef = useRef<Field>(null);
|
||||||
|
|
||||||
// Clear selection when the search results change
|
// Clear selection when the search results change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const SuggestionsInput = ({
|
|||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = getStyles(theme, inputHeight);
|
const styles = getStyles(theme, inputHeight);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>();
|
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollRef.current?.scrollTo(0, scrollTop);
|
scrollRef.current?.scrollTo(0, scrollTop);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import checkboxWhitePng from 'img/checkbox_white.png';
|
|||||||
|
|
||||||
import { ALL_VARIABLE_VALUE } from '../../constants';
|
import { ALL_VARIABLE_VALUE } from '../../constants';
|
||||||
|
|
||||||
export interface Props extends React.HTMLProps<HTMLUListElement>, Themeable2 {
|
export interface Props extends Omit<React.HTMLProps<HTMLUListElement>, 'onToggle'>, Themeable2 {
|
||||||
multi: boolean;
|
multi: boolean;
|
||||||
values: VariableOption[];
|
values: VariableOption[];
|
||||||
selectedValues: VariableOption[];
|
selectedValues: VariableOption[];
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"monaco-editor": "0.34.1",
|
"monaco-editor": "0.34.1",
|
||||||
"prismjs": "1.30.0",
|
"prismjs": "1.30.0",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-select": "5.10.2",
|
"react-select": "5.10.2",
|
||||||
"react-use": "17.6.0",
|
"react-use": "17.6.0",
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/prismjs": "1.26.5",
|
"@types/prismjs": "1.26.5",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"i18next-cli": "^1.24.22",
|
"i18next-cli": "^1.24.22",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"react-select-event": "5.5.1",
|
"react-select-event": "5.5.1",
|
||||||
|
|||||||
@@ -295,6 +295,9 @@ describe('VisualMetricQueryEditor', () => {
|
|||||||
const defaultQuery = { ...query, ...defaultTimeSeriesList(datasource), filters: query.filters };
|
const defaultQuery = { ...query, ...defaultTimeSeriesList(datasource), filters: query.filters };
|
||||||
const range = getDefaultTimeRange();
|
const range = getDefaultTimeRange();
|
||||||
|
|
||||||
|
// TODO investigate why we need act
|
||||||
|
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||||
|
await act(async () =>
|
||||||
render(
|
render(
|
||||||
<VisualMetricQueryEditor
|
<VisualMetricQueryEditor
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
@@ -303,6 +306,7 @@ describe('VisualMetricQueryEditor', () => {
|
|||||||
query={query}
|
query={query}
|
||||||
range={range}
|
range={range}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
expect(document.body).toHaveTextContent('metric.test_label');
|
expect(document.body).toHaveTextContent('metric.test_label');
|
||||||
expect(await screen.findByText('Delta')).toBeInTheDocument();
|
expect(await screen.findByText('Delta')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ exports[`VariableQueryEditor renders correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-8nwx1l-singleValue css-0"
|
class="css-8nwx1l-singleValue css-0"
|
||||||
>
|
>
|
||||||
Loading...
|
Projects
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-1eu65zc"
|
class="css-1eu65zc"
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"monaco-editor": "0.34.1",
|
"monaco-editor": "0.34.1",
|
||||||
"prismjs": "1.30.0",
|
"prismjs": "1.30.0",
|
||||||
"react": "18.3.1",
|
"react": "19.2.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.2.0",
|
||||||
"react-select": "5.10.2",
|
"react-select": "5.10.2",
|
||||||
"react-use": "17.6.0",
|
"react-use": "17.6.0",
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/prismjs": "1.26.5",
|
"@types/prismjs": "1.26.5",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "19.2.3",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"react-select-event": "5.5.1",
|
"react-select-event": "5.5.1",
|
||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ interface CodeEditorProps {
|
|||||||
export const LogsQLCodeEditor = (props: CodeEditorProps) => {
|
export const LogsQLCodeEditor = (props: CodeEditorProps) => {
|
||||||
const { query, datasource, onChange } = props;
|
const { query, datasource, onChange } = props;
|
||||||
|
|
||||||
const monacoRef = useRef<Monaco>();
|
const monacoRef = useRef<Monaco>(null);
|
||||||
const disposalRef = useRef<monacoType.IDisposable>();
|
const disposalRef = useRef<monacoType.IDisposable>(undefined);
|
||||||
|
|
||||||
const onFocus = useCallback(async () => {
|
const onFocus = useCallback(async () => {
|
||||||
disposalRef.current = await reRegisterCompletionProvider(
|
disposalRef.current = await reRegisterCompletionProvider(
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ interface LogsCodeEditorProps {
|
|||||||
export const PPLQueryEditor = (props: LogsCodeEditorProps) => {
|
export const PPLQueryEditor = (props: LogsCodeEditorProps) => {
|
||||||
const { query, datasource, onChange } = props;
|
const { query, datasource, onChange } = props;
|
||||||
|
|
||||||
const monacoRef = useRef<Monaco>();
|
const monacoRef = useRef<Monaco>(null);
|
||||||
const disposalRef = useRef<monacoType.IDisposable>();
|
const disposalRef = useRef<monacoType.IDisposable>(undefined);
|
||||||
|
|
||||||
const onFocus = useCallback(async () => {
|
const onFocus = useCallback(async () => {
|
||||||
disposalRef.current = await reRegisterCompletionProvider(
|
disposalRef.current = await reRegisterCompletionProvider(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user