Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/gh-actions-self-hosted-runners/arc/images/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@

* X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)).
* [YAML] WriteToTFRecord and ReadFromTFRecord Beam YAML support
* 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)).
* Yapf version upgraded to 0.43.0 for formatting (Python) ([#34801](https://github.com/apache/beam/pull/34801/)).
* Python: Added JupyterLab 4.x extension compatibility for enhanced notebook integration ([#34495](https://github.com/apache/beam/pull/34495)).

## Deprecations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Includes two different side panels:

| JupyterLab version | Extension version |
| ------------------ | ----------------- |
| v3 | >=v2.0.0 |
| v4 | >=v4.0.0 |
| v3 | v2.0.0-v3.0.0 |
| v2 | v1.0.0 |

## Install
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": ['<rootDir>/jest.setup.js']
}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -86,6 +95,6 @@
},
"test": "jest",
"resolutions": {
"@types/react": "~16.9.16"
"@types/react": "^18.2.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
ReactWidget,
SessionContext,
ISessionContext,
sessionContextDialogs
SessionContextDialogs
} from '@jupyterlab/apputils';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { ServiceManager } from '@jupyterlab/services';
Expand Down Expand Up @@ -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;
Expand All @@ -78,18 +78,19 @@ 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.
handleComms: false
}
});
// 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Clusters> = React.createRef<Clusters>();
act(() => {
render(
<Clusters sessionContext={{} as any} ref={clustersRef} />,
container
);
await act(async () => {
root.render(<Clusters sessionContext={{} as any} ref={clustersRef} />);
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<Clusters> = React.createRef<Clusters>();
const testData = {
key: {
Expand All @@ -59,17 +72,19 @@ it('renders a data-table', () => {
dashboard: 'test-dashboard'
}
};
act(() => {
render(
<Clusters sessionContext={{} as any} ref={clustersRef} />,
container
);
const clusters = clustersRef.current;
if (clusters) {
clusters.setState({ clusters: testData });
}
await act(async () => {
root.render(<Clusters sessionContext={{} as any} ref={clustersRef} />);
});

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(
Expand All @@ -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'
Expand Down
Loading
Loading