diff --git a/.github/gh-actions-self-hosted-runners/arc/images/Dockerfile b/.github/gh-actions-self-hosted-runners/arc/images/Dockerfile index 2fd9b41ca400..3af909de40ad 100644 --- a/.github/gh-actions-self-hosted-runners/arc/images/Dockerfile +++ b/.github/gh-actions-self-hosted-runners/arc/images/Dockerfile @@ -25,10 +25,10 @@ RUN docker buildx install && docker buildx version USER root #Install Node -RUN curl -OL https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-x64.tar.xz && \ - tar -C /usr/local -xf node-v18.16.0-linux-x64.tar.xz && \ - rm node-v18.16.0-linux-x64.tar.xz && \ - mv /usr/local/node-v18.16.0-linux-x64 /usr/local/node +RUN curl -OL https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.xz && \ + tar -C /usr/local -xf node-v22.14.0-linux-x64.tar.xz && \ + rm node-v22.14.0-linux-x64.tar.xz && \ + mv /usr/local/node-v22.14.0-linux-x64 /usr/local/node ENV PATH="${PATH}:/usr/local/node/bin" #Install Go ARG go_version=1.24.0 diff --git a/CHANGES.md b/CHANGES.md index fddd93b72750..ca2b47a055f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -72,11 +72,11 @@ ## New Features / Improvements -* X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). +* Python: Added JupyterLab 4.x extension compatibility for enhanced notebook integration ([#34495](https://github.com/apache/beam/pull/34495)). ## Breaking Changes -* X behavior was changed ([#X](https://github.com/apache/beam/issues/X)). +* Python: Added JupyterLab 4.x extension compatibility for enhanced notebook integration ([#34495](https://github.com/apache/beam/pull/34495)). ## Deprecations diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.config.js b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.config.js index c916ee9d6cbd..454138a060ae 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.config.js +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.config.js @@ -28,5 +28,7 @@ module.exports = { // Use identity-obj-proxy to load css and less files in tests. "moduleNameMapper": { "\\.(css|less)$": "identity-obj-proxy" - } + }, + "testEnvironment": "jsdom", + "setupFilesAfterEnv": ['/jest.setup.js'] } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.setup.js b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.setup.js new file mode 100644 index 000000000000..4008e1ff1c73 --- /dev/null +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.setup.js @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Configures jest async performance. + */ + +const { configure } = require('@testing-library/react'); +require('@testing-library/jest-dom'); + +configure({ + asyncUtilTimeout: 5000, + react: { version: 'detect' } +}); + +globalThis.IS_REACT_ACT_ENVIRONMENT = true; \ No newline at end of file diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/package.json b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/package.json index a3d204a25f5f..745303d3ad61 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/package.json +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/package.json @@ -1,6 +1,6 @@ { "name": "apache-beam-jupyterlab-sidepanel", - "version": "3.0.0", + "version": "4.0.0", "description": "A side panel providing information and controls to run Apache Beam notebooks interactively.", "keywords": [ "jupyter", @@ -43,38 +43,47 @@ "watch:src": "tsc -w" }, "dependencies": { - "@jupyterlab/application": "^3.1.17", - "@jupyterlab/launcher": "^3.1.17", - "@jupyterlab/mainmenu": "^3.1.17", - "@rmwc/button": "^6.1.3", - "@rmwc/fab": "^6.1.4", - "@rmwc/data-table": "^6.0.14", - "@rmwc/dialog": "^7.0.2", - "@rmwc/drawer": "^6.0.14", - "@rmwc/list": "^6.1.3", - "@rmwc/textfield": "^6.1.4", - "@rmwc/tooltip": "^6.1.4", - "@rmwc/top-app-bar": "^6.1.3", - "material-design-icons": "^3.0.1" + "@jupyterlab/application": "^4.3.6", + "@jupyterlab/launcher": "^4.3.6", + "@jupyterlab/mainmenu": "^4.3.6", + "@lumino/widgets": "^2.2.1", + "@rmwc/base": "^14.0.0", + "@rmwc/button": "^8.0.6", + "@rmwc/data-table": "^8.0.6", + "@rmwc/dialog": "^8.0.6", + "@rmwc/drawer": "^8.0.6", + "@rmwc/fab": "^8.0.6", + "@rmwc/list": "^8.0.6", + "@rmwc/ripple": "^14.0.0", + "@rmwc/textfield": "^8.0.6", + "@rmwc/tooltip": "^8.0.6", + "@rmwc/top-app-bar": "^8.0.6", + "material-design-icons": "^3.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { - "@jupyterlab/builder": "^3.1.0", - "@types/jest": "^26.0.7", - "@types/react-dom": "^16.9.8", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", - "eslint": "^7.14.0", - "eslint-config-prettier": "^6.15.0", - "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-react": "^7.20.5", + "@jupyterlab/builder": "^4.3.6", + "@testing-library/dom": "^9.3.0", + "@testing-library/jest-dom": "^6.1.4", + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.14", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", "identity-obj-proxy": "^3.0.0", - "jest": "^26.1.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.0.0", "npm-run-all": "^4.1.5", - "prettier": "^2.1.1", - "react-dom": "^17.0.1", - "rimraf": "^3.0.2", - "ts-jest": "^26.1.3", - "typescript": "~4.1.3" + "prettier": "^3.2.4", + "rimraf": "^5.0.5", + "ts-jest": "^29.1.2", + "typescript": "~5.3.3" }, "sideEffects": [ "style/*.css", @@ -86,6 +95,6 @@ }, "test": "jest", "resolutions": { - "@types/react": "~16.9.16" + "@types/react": "^18.2.0" } } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/SidePanel.ts b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/SidePanel.ts index 596f4d3673c4..fb86b0a53fdf 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/SidePanel.ts +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/SidePanel.ts @@ -14,7 +14,7 @@ import { ReactWidget, SessionContext, ISessionContext, - sessionContextDialogs + SessionContextDialogs } from '@jupyterlab/apputils'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ServiceManager } from '@jupyterlab/services'; @@ -64,7 +64,7 @@ export class SidePanel extends BoxPanel { } else { let sessionModel = sessionModelItr.next(); while (sessionModel !== undefined) { - if (sessionModel.kernel.id !== firstModel.kernel.id) { + if (sessionModel.value.kernel.id !== firstModel.value.kernel.id) { // There is more than one unique running kernel. onlyOneUniqueKernelExists = false; break; @@ -78,7 +78,7 @@ export class SidePanel extends BoxPanel { // kernel. if (onlyOneUniqueKernelExists) { this._sessionContext.sessionManager.connectTo({ - model: firstModel, + model: firstModel.value, kernelConnectionOptions: { // Only one connection can handleComms. Leave it to the connection // established by the opened notebook. @@ -86,10 +86,11 @@ export class SidePanel extends BoxPanel { } }); // Connect to the unique kernel. - this._sessionContext.changeKernel(firstModel.kernel); + this._sessionContext.changeKernel(firstModel.value.kernel); } else { // Let the user choose among sessions and kernels when there is no // or more than 1 running kernels. + const sessionContextDialogs = new SessionContextDialogs(); await sessionContextDialogs.selectKernel(this._sessionContext); } } catch (err) { diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/clusters/Clusters.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/clusters/Clusters.test.tsx index 632e7c5df44a..f323875ed6f8 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/clusters/Clusters.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/clusters/Clusters.test.tsx @@ -12,42 +12,55 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { Clusters } from '../../clusters/Clusters'; +import { waitFor } from '@testing-library/dom'; + let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + container = null; + root = null; + } }); - -it('renders info message about no clusters being available', () => { +it('renders info message about no clusters being available', async () => { const clustersRef: React.RefObject = React.createRef(); - act(() => { - render( - , - container - ); + await act(async () => { + root.render(); const clusters = clustersRef.current; if (clusters) { clusters.setState({ clusters: {} }); } }); - const infoElement: Element = container.firstElementChild; + const infoElement = container.firstElementChild as Element; expect(infoElement.tagName).toBe('DIV'); expect(infoElement.textContent).toBe('No clusters detected.'); }); -it('renders a data-table', () => { +it('renders a data-table', async () => { const clustersRef: React.RefObject = React.createRef(); const testData = { key: { @@ -59,17 +72,19 @@ it('renders a data-table', () => { dashboard: 'test-dashboard' } }; - act(() => { - render( - , - container - ); - const clusters = clustersRef.current; - if (clusters) { - clusters.setState({ clusters: testData }); - } + await act(async () => { + root.render(); + }); + + await act(async () => { + clustersRef.current?.setState({ clusters: testData }); }); - const topAppBarHeader: Element = container.firstElementChild; + + await waitFor(() => + expect(container.querySelector('.mdc-data-table__table')).toBeTruthy() + ); + + const topAppBarHeader = container.firstElementChild as Element; expect(topAppBarHeader.tagName).toBe('HEADER'); expect(topAppBarHeader.getAttribute('class')).toContain('mdc-top-app-bar'); expect(topAppBarHeader.getAttribute('class')).toContain( @@ -79,42 +94,42 @@ it('renders a data-table', () => { 'mdc-top-app-bar--dense' ); expect(topAppBarHeader.innerHTML).toContain('Clusters [kernel:no kernel]'); - const topAppBarFixedAdjust: Element = container.children[1]; + const topAppBarFixedAdjust = container.children[1] as Element; expect(topAppBarFixedAdjust.tagName).toBe('DIV'); expect(topAppBarFixedAdjust.getAttribute('class')).toContain( 'mdc-top-app-bar--fixed-adjust' ); - const selectBar: Element = container.children[2]; + const selectBar = container.children[2] as Element; expect(selectBar.tagName).toBe('DIV'); expect(selectBar.getAttribute('class')).toContain('mdc-select'); - const dialogBox: Element = container.children[3]; + const dialogBox = container.children[3] as Element; expect(dialogBox.tagName).toBe('DIV'); expect(dialogBox.getAttribute('class')).toContain('mdc-dialog'); - const clustersComponent: Element = container.children[4]; + const clustersComponent = container.children[4] as Element; expect(clustersComponent.tagName).toBe('DIV'); expect(clustersComponent.getAttribute('class')).toContain('Clusters'); - const dataTableDiv: Element = clustersComponent.children[0]; + const dataTableDiv = clustersComponent.children[0] as Element; expect(dataTableDiv.tagName).toBe('DIV'); expect(dataTableDiv.getAttribute('class')).toContain('mdc-data-table'); - const dataTable: Element = dataTableDiv.children[0]; + const dataTable = dataTableDiv.children[0].firstElementChild as Element; expect(dataTable.tagName).toBe('TABLE'); expect(dataTable.getAttribute('class')).toContain('mdc-data-table__table'); - const dataTableHead: Element = dataTable.children[0]; + const dataTableHead = dataTable.children[0] as Element; expect(dataTableHead.tagName).toBe('THEAD'); expect(dataTableHead.getAttribute('class')).toContain( 'rmwc-data-table__head' ); - const dataTableHeaderRow: Element = dataTableHead.children[0]; + const dataTableHeaderRow = dataTableHead.children[0] as Element; expect(dataTableHeaderRow.tagName).toBe('TR'); expect(dataTableHeaderRow.getAttribute('class')).toContain( 'mdc-data-table__header-row' ); - const dataTableBody: Element = dataTable.children[1]; + const dataTableBody = dataTable.children[1] as Element; expect(dataTableBody.tagName).toBe('TBODY'); expect(dataTableBody.getAttribute('class')).toContain( 'mdc-data-table__content' ); - const dataTableBodyRow: Element = dataTableBody.children[0]; + const dataTableBodyRow = dataTableBody.children[0] as Element; expect(dataTableBodyRow.tagName).toBe('TR'); expect(dataTableBodyRow.getAttribute('class')).toContain( 'mdc-data-table__row' diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/common/HtmlView.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/common/HtmlView.test.tsx index a6183d60ba3d..2aa8be3fb5a7 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/common/HtmlView.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/common/HtmlView.test.tsx @@ -16,46 +16,64 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { HtmlView, IHtmlProvider, importHtml } from '../../common/HtmlView'; +import { waitFor } from '@testing-library/dom'; let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - jest.clearAllMocks(); +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + jest.clearAllMocks(); + container = null; + root = null; + } }); describe('HtmlView', () => { - it('renders provided html', () => { + it('renders provided html', async () => { const htmlViewRef: React.RefObject = React.createRef(); const spiedConsole = jest.spyOn(console, 'log'); const fakeHtmlProvider = { html: '
Test
', script: ['console.log(1);', 'console.log(2);'] } as IHtmlProvider; - act(() => { - render( - , - container + await act(async () => { + root.render( + ); - const htmlView = htmlViewRef.current; - if (htmlView) { - htmlView.updateRender(); - } }); - const htmlViewElement: Element = container.firstElementChild; - expect(htmlViewElement.tagName).toBe('DIV'); - expect(htmlViewElement.innerHTML).toBe('
Test
'); + await act(async () => { + htmlViewRef.current?.updateRender(); + }); + + await waitFor(() => { + const htmlViewElement = container.firstElementChild as Element; + expect(htmlViewElement.tagName).toBe('DIV'); + expect(htmlViewElement.innerHTML).toBe('
Test
'); + }); + expect(spiedConsole).toHaveBeenCalledWith(1); expect(spiedConsole).toHaveBeenCalledWith(2); expect(spiedConsole).toHaveBeenCalledTimes(2); @@ -64,7 +82,7 @@ describe('HtmlView', () => { it( 'only executes incrementally updated Javascript ' + 'as html provider updated', - () => { + async () => { const htmlViewRef: React.RefObject = React.createRef(); const spiedConsole = jest.spyOn(console, 'log'); @@ -72,20 +90,23 @@ describe('HtmlView', () => { html: '
', script: ['console.log(1);'] } as IHtmlProvider; - act(() => { - render( - , - container + await act(async () => { + root.render( + ); - const htmlView = htmlViewRef.current; - if (htmlView) { - htmlView.updateRender(); - } }); - expect(spiedConsole).toHaveBeenCalledWith(1); - expect(spiedConsole).toHaveBeenCalledTimes(1); + await act(async () => { + htmlViewRef.current?.updateRender(); + }); + + await waitFor(() => { + expect(spiedConsole).toHaveBeenCalledWith(1); + expect(spiedConsole).toHaveBeenCalledTimes(1); + }); + fakeHtmlProvider.script.push('console.log(2);'); - act(() => { + + await act(async () => { const htmlView = htmlViewRef.current; if (htmlView) { htmlView.updateRender(); @@ -97,11 +118,11 @@ describe('HtmlView', () => { ); }); describe('Function importHtml', () => { - it('imports webcomponents script', () => { - act(() => { + it('imports webcomponents script', async () => { + await act(async () => { importHtml([]); }); - const scriptElement: Element = document.head.firstElementChild; + const scriptElement = document.head.firstElementChild as Element; expect(scriptElement.tagName).toBe('SCRIPT'); expect(scriptElement.getAttribute('src')).toBe( 'https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js' diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableList.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableList.test.tsx index 4e6ba342427a..1b475bdbc182 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableList.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableList.test.tsx @@ -12,9 +12,9 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { InspectableList } from '../../inspector/InspectableList'; @@ -23,20 +23,35 @@ import { InspectableViewModel } from '../../inspector/InspectableViewModel'; const mockedInspectableViewModel = new InspectableViewModel({} as any); let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + container = null; + root = null; + } }); -it('renders a list', () => { - act(() => { - render( +it('renders a list', async () => { + await act(async () => { + root.render( { type: 'pcollection' } }} - />, - container + /> ); }); - const listElement: Element = container.firstElementChild; - const listHandle: Element = listElement.firstElementChild; + const listElement = container.firstElementChild as Element; + const listHandle = listElement.firstElementChild as Element; expect(listHandle.tagName).toBe('DIV'); expect(listHandle.getAttribute('class')).toContain( 'rmwc-collapsible-list__handle' ); - const listHandleItem: Element = listHandle.firstElementChild; + const listHandleItem = listHandle.firstElementChild as Element; expect(listHandleItem.tagName).toBe('LI'); expect(listHandleItem.getAttribute('class')).toContain('mdc-list-item'); - const listHandleText: Element = listHandleItem.firstElementChild; + const listHandleText = listHandleItem.children[2] as Element; expect(listHandleText.getAttribute('class')).toContain('mdc-list-item__text'); - const listHandlePrimaryText: Element = listHandleText.firstElementChild; + const listHandlePrimaryText = listHandleText.firstElementChild as Element; expect(listHandlePrimaryText.getAttribute('class')).toContain( 'mdc-list-item__primary-text' ); expect(listHandlePrimaryText.textContent).toBe('pipeline_name'); - const listHandleMetaIcon: Element = listHandleItem.children[1]; + const listHandleMetaIcon = listHandleItem.children[3] as Element; expect(listHandleMetaIcon.tagName).toBe('I'); expect(listHandleMetaIcon.getAttribute('class')).toContain( 'mdc-list-item__meta' @@ -85,12 +99,12 @@ it('renders a list', () => { expect(listHandleMetaIcon.textContent).toBe('chevron_right'); // Only check existence of collapsible list children because each child is an // individual list item that has its own unit tests. - const listChildren: Element = listElement.children[1]; + const listChildren = listElement.children[1] as Element; expect(listChildren.tagName).toBe('DIV'); expect(listChildren.getAttribute('class')).toContain( 'rmwc-collapsible-list__children' ); - const listChildItems: HTMLCollection = - listChildren.firstElementChild.children; + const listChildItems = listChildren.firstElementChild + .children as HTMLCollection; expect(listChildItems).toHaveLength(2); }); diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableListItem.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableListItem.test.tsx index 9019c2cf6efa..6bf3bd6f0f46 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableListItem.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableListItem.test.tsx @@ -12,27 +12,42 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { InspectableListItem } from '../../inspector/InspectableListItem'; let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + container = null; + root = null; + } }); -it('renders an item', () => { - act(() => { - render( +it('renders an item', async () => { + await act(async () => { + root.render( { inMemoryId: 123456, type: 'pcollection' }} - />, - container + /> ); }); - const liElement: Element = container.firstElementChild; + const liElement = container.firstElementChild as Element; expect(liElement.tagName).toBe('LI'); expect(liElement.getAttribute('class')).toBe('mdc-list-item'); - const textElement: Element = liElement.firstElementChild; + const textElement = liElement.children[1] as Element; expect(textElement.getAttribute('class')).toBe('mdc-list-item__text'); - const primaryTextElement: Element = textElement.firstElementChild; + const primaryTextElement = textElement.firstElementChild as Element; expect(primaryTextElement.getAttribute('class')).toBe( 'mdc-list-item__primary-text' ); expect(primaryTextElement.textContent).toBe('name'); - const secondaryTextElement: Element = textElement.children[1]; + const secondaryTextElement = textElement.children[1] as Element; expect(secondaryTextElement.getAttribute('class')).toBe( 'mdc-list-item__secondary-text' ); diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableView.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableView.test.tsx index 2a7dd5a7ed64..9ef77b191577 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableView.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableView.test.tsx @@ -12,9 +12,9 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { InspectableView } from '../../inspector/InspectableView'; @@ -24,18 +24,33 @@ import { } from '../../inspector/InspectableViewModel'; let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + container = null; + root = null; + } }); -it('does not render options if inspecting a pipeline', () => { +it('does not render options if inspecting a pipeline', async () => { const fakeModel = { html: '', script: [] as string[], @@ -43,8 +58,8 @@ it('does not render options if inspecting a pipeline', () => { identifier: 'id', options: {} as IOptions } as InspectableViewModel; - act(() => { - render(, container); + await act(async () => { + root.render(); }); const inspectableViewElement: Element = container.firstElementChild; const optionsElement: Element = inspectableViewElement.firstElementChild; @@ -52,7 +67,7 @@ it('does not render options if inspecting a pipeline', () => { expect(optionsElement.innerHTML).toBe(''); }); -it('renders options if inspecting a pcollection', () => { +it('renders options if inspecting a pcollection', async () => { const inspectableViewRef: React.RefObject = React.createRef(); const fakeModel = { @@ -65,11 +80,8 @@ it('renders options if inspecting a pcollection', () => { visualizeInFacets: true } as IOptions } as InspectableViewModel; - act(() => { - render( - , - container - ); + await act(async () => { + root.render(); const inspectableView = inspectableViewRef.current; if (inspectableView) { inspectableView.setState({ @@ -77,37 +89,37 @@ it('renders options if inspecting a pcollection', () => { }); } }); - const inspectableViewElement: Element = container.firstElementChild; - const optionsElement: Element = inspectableViewElement.firstElementChild; + const optionsElement = container.firstElementChild + .firstElementChild as Element; expect(optionsElement.tagName).toBe('DIV'); - const includeWindowInfoCheckbox: Element = - optionsElement.firstElementChild.firstElementChild; + const includeWindowInfoCheckbox = optionsElement.firstElementChild + .firstElementChild as Element; expect( includeWindowInfoCheckbox.firstElementChild.getAttribute('class') ).toContain('mdc-checkbox'); expect( includeWindowInfoCheckbox.firstElementChild.getAttribute('class') ).not.toContain('mdc-checkbox--selected'); - const visualizeInFacetsCheckbox: Element = - optionsElement.firstElementChild.children[1]; + const visualizeInFacetsCheckbox = optionsElement.firstElementChild + .children[1] as Element; expect( visualizeInFacetsCheckbox.firstElementChild.getAttribute('class') ).toContain('mdc-checkbox'); expect( visualizeInFacetsCheckbox.firstElementChild.getAttribute('class') ).toContain('mdc-checkbox--selected'); - const durationTextField: Element = - optionsElement.firstElementChild.children[2]; + const durationTextField = optionsElement.firstElementChild + .children[2] as Element; expect(durationTextField.getAttribute('class')).toContain( 'mdc-text-field--outlined' ); - const nTextField: Element = optionsElement.firstElementChild.children[3]; + const nTextField = optionsElement.firstElementChild.children[3] as Element; expect(nTextField.getAttribute('class')).toContain( 'mdc-text-field--outlined' ); }); -it('renders an html view', () => { +it('renders an html view', async () => { const fakeModel = { html: '
fake html
', script: ['console.log(1)'], @@ -115,9 +127,9 @@ it('renders an html view', () => { identifier: 'id', options: {} as IOptions } as InspectableViewModel; - act(() => { - render(, container); + await act(async () => { + root.render(); }); - const inspectableViewElement: Element = container.firstElementChild; + const inspectableViewElement = container.firstElementChild as Element; expect(inspectableViewElement.innerHTML).toContain('
fake html
'); }); diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/Inspectables.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/Inspectables.test.tsx index 3f0f3fbc4a22..bca3568b5126 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/Inspectables.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/Inspectables.test.tsx @@ -12,11 +12,12 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { Inspectables } from '../../inspector/Inspectables'; +import { waitFor } from '@testing-library/dom'; jest.mock('../../inspector/InspectableList', () => { const FakeInspectableList = function (): React.ReactNode { @@ -30,36 +31,52 @@ jest.mock('../../inspector/InspectableList', () => { }); let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - jest.clearAllMocks(); +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + jest.clearAllMocks(); + container = null; + root = null; + } }); -it('renders info message about no inspectable when none is available', () => { +it(`renders info message about no inspectable + when none is available`, async () => { const inspectablesRef: React.RefObject = React.createRef(); - act(() => { - render(, container); + await act(async () => { + root.render(); const inspectables = inspectablesRef.current; if (inspectables) { inspectables.setState({ inspectables: {} }); } }); - const infoElement: Element = container.firstElementChild; + const infoElement = container.firstElementChild as Element; expect(infoElement.tagName).toBe('DIV'); expect(infoElement.textContent).toBe( 'No inspectable pipeline nor pcollection has been defined.' ); }); -it('renders inspectables as a list of collapsible lists', () => { +it('renders inspectables as a list of collapsible lists', async () => { const inspectablesRef: React.RefObject = React.createRef(); const testData = { @@ -92,17 +109,21 @@ it('renders inspectables as a list of collapsible lists', () => { } } }; - act(() => { - render(, container); - const inspectables = inspectablesRef.current; - if (inspectables) { - inspectables.setState({ inspectables: testData }); - } + + await act(async () => { + root.render(); + }); + + await act(async () => { + inspectablesRef.current?.setState({ inspectables: testData }); + }); + + await waitFor(() => { + const listElement = container.firstElementChild as Element; + expect(listElement.tagName).toBe('UL'); + expect(listElement.getAttribute('class')).toContain('mdc-list'); + // Only checks the length of dummy InspectableList items. + // Each InspectableList has its own unit tests. + expect(listElement.children).toHaveLength(2); }); - const listElement: Element = container.firstElementChild; - expect(listElement.tagName).toBe('UL'); - expect(listElement.getAttribute('class')).toContain('mdc-list'); - // Only checks the length of dummy InspectableList items. Each InspectableList - // has its own unit tests. - expect(listElement.children).toHaveLength(2); }); diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InteractiveInspector.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InteractiveInspector.test.tsx index 39be6904aa54..b5215b3b9874 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InteractiveInspector.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InteractiveInspector.test.tsx @@ -12,13 +12,14 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { InteractiveInspector } from '../../inspector/InteractiveInspector'; import { InspectableViewModel } from '../../inspector/InspectableViewModel'; +import { waitFor } from '@testing-library/dom'; const fakeSessionContext = { session: { @@ -36,31 +37,45 @@ const fakeSessionContext = { }; let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + container = null; + root = null; + } }); -it('renders the top app bar and drawer wrapped inspectables', () => { +it('renders the top app bar and drawer wrapped inspectables', async () => { const inspectableViewModel = new InspectableViewModel( fakeSessionContext as any ); - act(() => { - render( + await act(async () => { + root.render( , - container + /> ); }); - const topAppBarHeader: Element = container.firstElementChild; + const topAppBarHeader = container.firstElementChild as Element; expect(topAppBarHeader.tagName).toBe('HEADER'); expect(topAppBarHeader.getAttribute('class')).toContain('mdc-top-app-bar'); expect(topAppBarHeader.getAttribute('class')).toContain( @@ -71,17 +86,18 @@ it('renders the top app bar and drawer wrapped inspectables', () => { ); expect(topAppBarHeader.innerHTML).toContain('menu'); expect(topAppBarHeader.innerHTML).toContain('Inspector [kernel:no kernel]'); - const topAppBarFixedAdjust: Element = container.children[1]; + const topAppBarFixedAdjust = container.children[1] as Element; expect(topAppBarFixedAdjust.tagName).toBe('DIV'); expect(topAppBarFixedAdjust.getAttribute('class')).toContain( 'mdc-top-app-bar--fixed-adjust' ); - const interactiveInspectorDiv: Element = container.children[2]; + const interactiveInspectorDiv = container.children[2] as Element; expect(interactiveInspectorDiv.tagName).toBe('DIV'); expect(interactiveInspectorDiv.getAttribute('class')).toContain( 'InteractiveInspector' ); - const inspectablesAside: Element = interactiveInspectorDiv.firstElementChild; + const inspectablesAside = + interactiveInspectorDiv.firstElementChild as Element; expect(inspectablesAside.tagName).toBe('ASIDE'); expect(inspectablesAside.innerHTML).toContain( '
No inspectable pipeline nor pcollection has been defined.
' @@ -89,8 +105,8 @@ it('renders the top app bar and drawer wrapped inspectables', () => { expect(inspectablesAside.firstElementChild.getAttribute('class')).toContain( 'mdc-drawer__content' ); - const inspectableViewAsAppContent: Element = - interactiveInspectorDiv.children[1]; + const inspectableViewAsAppContent = interactiveInspectorDiv + .children[1] as Element; expect(inspectableViewAsAppContent.tagName).toBe('DIV'); expect(inspectableViewAsAppContent.getAttribute('class')).toContain( 'mdc-drawer-app-content' @@ -100,72 +116,85 @@ it('renders the top app bar and drawer wrapped inspectables', () => { ).toContain('InspectableView'); }); -it('renders the drawer open by default', () => { +it('renders the drawer open by default', async () => { const inspectableViewModel = new InspectableViewModel( fakeSessionContext as any ); - act(() => { - render( + await act(async () => { + root.render( , - container + /> ); }); - const inspectablesAside: Element = container.children[2].firstElementChild; + const inspectablesAside = container.children[2].firstElementChild as Element; expect(inspectablesAside.getAttribute('class')).toContain('mdc-drawer--open'); }); -it('closes the drawer on flip from open state', () => { +it('closes the drawer on flip from open state', async () => { const inspectorRef: React.RefObject = React.createRef(); const inspectableViewModel = new InspectableViewModel( fakeSessionContext as any ); - act(() => { - render( + await act(async () => { + root.render( , - container + /> ); + }); + + await act(async () => { const inspector = inspectorRef.current; if (inspector) { inspector.flipDrawer(); } }); + // react test renderer does not re-render the drawer component even if the // state is changed. Test the state change instead of DOM change. - const inspector = inspectorRef.current; - if (inspector) { - expect(inspector.state.drawerOpen).toBe(false); - } + await waitFor(() => { + const updatedInspector = inspectorRef.current; + if (updatedInspector) { + expect(updatedInspector.state.drawerOpen).toBe(false); // 确保抽屉被打开 + } + }); }); -it('updates session info on change', () => { +it('updates session info on change', async () => { const inspectorRef: React.RefObject = React.createRef(); const inspectableViewModel = new InspectableViewModel( fakeSessionContext as any ); - act(() => { - render( + + await act(async () => { + root.render( , - container + /> ); + }); + + await act(async () => { const inspector = inspectorRef.current; if (inspector) { fakeSessionContext.kernelDisplayName = 'new kernel'; inspector.updateSessionInfo(); } }); - const topAppBarHeader: Element = container.firstElementChild; - expect(topAppBarHeader.innerHTML).toContain('Inspector [kernel:new kernel]'); + + await waitFor(() => { + const topAppBarHeader = container.firstElementChild.firstElementChild + .firstElementChild.children[1] as Element; + expect(topAppBarHeader.innerHTML).toContain( + 'Inspector [kernel:new kernel]' + ); + }); }); diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/kernel/InterruptKernelButton.test.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/kernel/InterruptKernelButton.test.tsx index c30bc05cbcca..d9844b0f2eb2 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/kernel/InterruptKernelButton.test.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/kernel/InterruptKernelButton.test.tsx @@ -12,84 +12,109 @@ import * as React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { InterruptKernelButton } from '../../kernel/InterruptKernelButton'; +import { waitFor } from '@testing-library/react'; + const fakeKernelModel = { isDone: true, interruptKernel: function (): void { // do nothing. } }; -let container: null | Element = null; +let container: null | Element = null; +let root: Root | null = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); + root = createRoot(container); }); -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - jest.clearAllMocks(); - fakeKernelModel.isDone = true; +afterEach(async () => { + try { + if (root) { + await act(async () => { + root.unmount(); + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } + } catch (error) { + console.warn('During unmount:', error); + } finally { + if (container?.parentNode) { + container.remove(); + } + jest.clearAllMocks(); + container = null; + root = null; + } }); -it('displays a button when the kernel model is not done with execution', () => { +it(`displays a button when the kernel model + is not done with execution`, async () => { let button: InterruptKernelButton; - act(() => { - render( + + await act(async () => { + root.render( { button = node; }} model={fakeKernelModel as any} - />, - container + /> ); + }); + + await act(async () => { fakeKernelModel.isDone = false; - if (button) { - button.updateRender(); - } + button?.updateRender(); }); - const buttonElement: null | Element = container.firstElementChild; - expect(buttonElement.tagName).toBe('BUTTON'); + + await waitFor(() => { + const button = container.firstElementChild; + expect(button).not.toBeNull(); + expect(button?.tagName).toBe('BUTTON'); + }); + const buttonElement = container.firstElementChild as Element; expect(buttonElement.getAttribute('class')).toContain('mdc-button'); expect(buttonElement.getAttribute('class')).toContain('mdc-button--raised'); - const labelElement: Element = buttonElement.children[1]; + const labelElement = buttonElement.children[1] as Element; expect(labelElement.tagName).toBe('SPAN'); expect(labelElement.getAttribute('class')).toContain('mdc-button__label'); expect(labelElement.innerHTML).toBe('stop'); }); -it('renders nothing when the kernel model is done with execution', () => { - act(() => { - render(, container); +it(`renders nothing when the kernel + model is done with execution`, async () => { + await act(async () => { + root.render(); }); const buttonElement: null | Element = container.firstElementChild; expect(buttonElement).toBe(null); }); -it('interrupts the kernel when clicked', () => { +it('interrupts the kernel when clicked', async () => { let button: InterruptKernelButton; - const spiedInterrruptCall = jest.spyOn(fakeKernelModel, 'interruptKernel'); - act(() => { - render( + const spiedInterruptCall = jest.spyOn(fakeKernelModel, 'interruptKernel'); + await act(async () => { + root.render( { button = node; }} model={fakeKernelModel as any} - />, - container + /> ); - if (button) { - button.onClick(); - } }); - expect(spiedInterrruptCall).toHaveBeenCalledTimes(1); + await act(async () => { + button?.onClick(); + await waitFor(() => { + expect(spiedInterruptCall).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/clusters/Clusters.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/clusters/Clusters.tsx index 51542563d238..b397b4cf6702 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/clusters/Clusters.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/clusters/Clusters.tsx @@ -209,7 +209,7 @@ export class Clusters extends React.Component { style={{ backgroundColor: 'var(--mdc-theme-error)' }} theme={['onError']} mini - onClick={e => { + onClick={() => { this.displayDialog(true, key, value['cluster_name']); }} /> @@ -234,7 +234,9 @@ export class Clusters extends React.Component { label="Default cluster" enhanced options={clusterNames} - onChange={e => this.setDefaultCluster(e.currentTarget.value)} + onChange={(e: React.ChangeEvent) => + this.setDefaultCluster(e.currentTarget.value) + } value={this.state.defaultClusterId} /> { { + onClick={() => { this.deleteCluster(this.state.selectedId); }} > @@ -286,7 +288,7 @@ export class Clusters extends React.Component { private _inspectKernelCode: string; private _model: KernelModel; - private _queryKernelTimerId: number; - private _updateRenderTimerId: number; - private _updateSessionInfoTimerId: number; + private _queryKernelTimerId: ReturnType; + private _updateRenderTimerId: ReturnType; + private _updateSessionInfoTimerId: ReturnType; } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/common/HtmlView.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/common/HtmlView.tsx index 1cd35b423cf5..2189d6eb2146 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/common/HtmlView.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/common/HtmlView.tsx @@ -88,7 +88,7 @@ export class HtmlView extends React.Component { ); } - private _updateRenderTimerId: number; + private _updateRenderTimerId: ReturnType; } /** diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableList.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableList.tsx index 90e262ced0a4..cde34653208d 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableList.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableList.tsx @@ -123,5 +123,5 @@ export class InspectableList extends React.Component< }); } - private _updateRenderTimerId: number; + private _updateRenderTimerId: ReturnType; } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableListItem.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableListItem.tsx index 9fa2075965a7..4abd846635de 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableListItem.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableListItem.tsx @@ -98,5 +98,5 @@ export class InspectableListItem extends React.Component< ); } - private _updateRenderTimerId: number; + private _updateRenderTimerId: ReturnType; } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableView.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableView.tsx index c04c45722a11..3c949e3126e9 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableView.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InspectableView.tsx @@ -131,7 +131,7 @@ export class InspectableView extends React.Component< { + onChange={(e: React.ChangeEvent): void => { showOptions.includeWindowInfo = !!e.currentTarget.checked; this.setState({ options: showOptions }); }} @@ -146,7 +146,7 @@ export class InspectableView extends React.Component< { + onChange={(e: React.ChangeEvent): void => { showOptions.visualizeInFacets = !!e.currentTarget.checked; this.setState({ options: showOptions }); }} @@ -169,7 +169,7 @@ export class InspectableView extends React.Component< label="Duration" floatLabel placeholder={showOptions.duration} - onChange={(e): void => { + onChange={(e: React.ChangeEvent): void => { showOptions.duration = e.currentTarget.value; }} /> @@ -190,7 +190,7 @@ export class InspectableView extends React.Component< label="Element Number" floatLabel placeholder={showOptions.n} - onChange={(e): void => { + onChange={(e: React.ChangeEvent): void => { showOptions.n = e.currentTarget.value; }} /> @@ -220,5 +220,5 @@ export class InspectableView extends React.Component< } as IShowOptions; } - private _updateRenderTimerId: number; + private _updateRenderTimerId: ReturnType; } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/Inspectables.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/Inspectables.tsx index a38b3b994b01..a6235c4358d4 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/Inspectables.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/Inspectables.tsx @@ -99,6 +99,6 @@ export class Inspectables extends React.Component< private _inspectKernelCode: string; private _model: KernelModel; - private _queryKernelTimerId: number; - private _updateRenderTimerId: number; + private _queryKernelTimerId: ReturnType; + private _updateRenderTimerId: ReturnType; } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InteractiveInspector.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InteractiveInspector.tsx index 854acb2ebd19..9eaffd1588d9 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InteractiveInspector.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/inspector/InteractiveInspector.tsx @@ -129,5 +129,5 @@ export class InteractiveInspector extends React.Component< ); } - private _updateSessionInfoTimerId: number; + private _updateSessionInfoTimerId: ReturnType; } diff --git a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/kernel/InterruptKernelButton.tsx b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/kernel/InterruptKernelButton.tsx index 0ab9be7f516a..9bbe7b3e2e33 100644 --- a/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/kernel/InterruptKernelButton.tsx +++ b/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/kernel/InterruptKernelButton.tsx @@ -78,5 +78,5 @@ export class InterruptKernelButton extends React.Component< return