diff --git a/README.md b/README.md index 5618976194..aca298fc40 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,10 @@ We use Chrome for development with the React and Redux extensions. ## Scripts - `npm install` : Install all dependencies and automatically bootstrap packages. Should be run before any of the other steps. -- `npm start`: Start building all packages and watching them (when possible). Use when you're developing, and your changes will be picked up automatically. +- `npm start`: Start building all packages and watching them (when possible). Use when you're developing, and your changes will be picked up automatically. Servers will open up for code-studio, embed-grid, embed-chart, and embed-widget. These will open on localhost on ports 4000, 4010, 4020, and 4030 respectively. - `npm test`: Start running tests in all packages and watching (when possible). Use when you're developing, and any breaking changes will be printed out automatically. See [Unit tests](#unit-tests) for more details. - `npm run build`: Create a production build of all packages. Mainly used by CI when packaging up a production version of the app. -- `npm run preview`: Runs the Vite preview server for the built code-studio, embed-grid, and embed-chart. These will open on ports 4000, 4010, and 4020. +- `npm run preview`: Runs the Vite preview server for the built code-studio, embed-grid, embed-chart, and embed-widget. These will open on ports 4000, 4010, 4020, and 4030 respectively. - `npm run e2e`: Runs the Playwright end-to-end tests locally. See [E2E Tests](#e2e-tests) for more details. If your DHC address is different from the default `http://localhost:10000`, edit `.env.local` in each package to point to your local DHC. This is needed for the session websocket and for things like notebooks to be proxied correctly by Vite. @@ -134,6 +134,7 @@ Note that log messages from other sources such as react prop types will still be If you want to collect coverage locally, run `npm test -- --coverage` ### Debugging Unit Tests + Unit tests can be debugged by running jest with the `--inspect-brk` flag and attaching to the node process in vscode's debugger. There are 2 launch configs that make this easier: - Debug Jest Tests - This will prompt you for a test name or pattern and will then run tests in watch mode with an attached debugger. @@ -152,6 +153,7 @@ Snapshots are used by end-to-end tests to visually verify the output. Snapshots Once you are satisfied with the snapshots and everything is passing locally, you need to use the docker image to update snapshots for CI (unless you are running the same platform as CI (Ubuntu)). Run `npm run e2e:update-ci-snapshots` to update the CI snapshots. The snapshots will be written to your local directories. The Linux snapshots should be committed to git (non-Linux snapshots should be automatically ignored by git). ### Differences in CI vs Local Docker Environments + Note that while both the GH actions and docker configuration use Ubuntu 22.04 images, their configurations are not identical. One known difference is the available system fonts. In some cases this can cause snapshots to differ when running locally vs in CI such as when rendering unicode characters. To mitigate this, some of our e2e tests have been configured to ensure a consistent unicode font fallback. - The `DejaVu Sans` font gets installed via the [Dockerfile](tests/docker-scripts/Dockerfile). It already exists in the CI environment. diff --git a/package-lock.json b/package-lock.json index 8f56af58e7..4642b5c5e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@deephaven/dashboard-core-plugins": "file:packages/dashboard-core-plugins", "@deephaven/embed-chart": "file:packages/embed-chart", "@deephaven/embed-grid": "file:packages/embed-grid", + "@deephaven/embed-widget": "file:packages/embed-widget", "@deephaven/file-explorer": "file:packages/file-explorer", "@deephaven/filters": "file:packages/filters", "@deephaven/golden-layout": "file:packages/golden-layout", @@ -2046,6 +2047,10 @@ "resolved": "packages/embed-grid", "link": true }, + "node_modules/@deephaven/embed-widget": { + "resolved": "packages/embed-widget", + "link": true + }, "node_modules/@deephaven/eslint-config": { "resolved": "packages/eslint-config", "link": true @@ -28282,7 +28287,6 @@ "dependencies": { "@deephaven/components": "file:../components", "@deephaven/golden-layout": "file:../golden-layout", - "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/log": "file:../log", "@deephaven/react-hooks": "file:../react-hooks", "@deephaven/redux": "file:../redux", @@ -28427,6 +28431,31 @@ "@deephaven/stylelint-config": "file:../stylelint-config" } }, + "packages/embed-widget": { + "name": "@deephaven/embed-widget", + "version": "0.55.0", + "license": "Apache-2.0", + "dependencies": { + "@deephaven/app-utils": "file:../app-utils", + "@deephaven/components": "file:../components", + "@deephaven/dashboard-core-plugins": "file:../dashboard-core-plugins", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", + "@deephaven/jsapi-components": "file:../jsapi-components", + "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/jsapi-utils": "file:../jsapi-utils", + "@deephaven/log": "file:../log", + "@deephaven/plugin": "file:../plugin", + "fira": "mozilla/fira#4.202", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@deephaven/eslint-config": "file:../eslint-config", + "@deephaven/mocks": "file:../mocks", + "@deephaven/prettier-config": "file:../prettier-config", + "@deephaven/stylelint-config": "file:../stylelint-config" + } + }, "packages/eslint-config": { "name": "@deephaven/eslint-config", "version": "0.55.0", @@ -30308,7 +30337,6 @@ "requires": { "@deephaven/components": "file:../components", "@deephaven/golden-layout": "file:../golden-layout", - "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/log": "file:../log", "@deephaven/mocks": "file:../mocks", "@deephaven/react-hooks": "file:../react-hooks", @@ -30422,6 +30450,27 @@ "react-dom": "^17.0.2" } }, + "@deephaven/embed-widget": { + "version": "file:packages/embed-widget", + "requires": { + "@deephaven/app-utils": "file:../app-utils", + "@deephaven/components": "file:../components", + "@deephaven/dashboard-core-plugins": "file:../dashboard-core-plugins", + "@deephaven/eslint-config": "file:../eslint-config", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", + "@deephaven/jsapi-components": "file:../jsapi-components", + "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/jsapi-utils": "file:../jsapi-utils", + "@deephaven/log": "file:../log", + "@deephaven/mocks": "file:../mocks", + "@deephaven/plugin": "file:../plugin", + "@deephaven/prettier-config": "file:../prettier-config", + "@deephaven/stylelint-config": "file:../stylelint-config", + "fira": "mozilla/fira#4.202", + "react": "^17.0.2", + "react-dom": "^17.0.2" + } + }, "@deephaven/eslint-config": { "version": "file:packages/eslint-config", "requires": { diff --git a/package.json b/package.json index e1e3040632..dd7f596efa 100644 --- a/package.json +++ b/package.json @@ -24,21 +24,23 @@ "types": "tsc --build", "watch:types": "tsc --build --watch --preserveWatchOutput", "build": "run-s build:necessary types build:packages build:apps", - "build:apps": "lerna run --scope=@deephaven/{code-studio,embed-chart,embed-grid} build", + "build:apps": "lerna run --scope=@deephaven/{code-studio,embed-chart,embed-grid,embed-widget} build", "build:app": "lerna run --scope=@deephaven/code-studio build", "build:embed-chart": "lerna run --scope=@deephaven/embed-chart build", "build:embed-grid": "lerna run --scope=@deephaven/embed-grid build", - "build:packages": "lerna run --ignore=@deephaven/{code-studio,embed-chart,embed-grid} build --stream", + "build:embed-widget": "lerna run --scope=@deephaven/embed-widget build", + "build:packages": "lerna run --ignore=@deephaven/{code-studio,embed-chart,embed-grid,embed-widget} build --stream", "build:profile": "lerna run build --stream --profile", "build:necessary": "lerna run build --scope=@deephaven/icons", "analyze": "lerna run analyze", - "preview": "lerna run --scope=@deephaven/{code-studio,embed-chart,embed-grid} preview --stream", + "preview": "lerna run --scope=@deephaven/{code-studio,embed-chart,embed-grid,embed-widget} preview --stream", "preview:app": "lerna run --scope=@deephaven/code-studio preview --stream", "prestart": "npm run build:necessary", "start": "run-p watch:types start:*", "start:app": "lerna run start --scope=@deephaven/code-studio --stream", "start:embed-chart": "lerna run start --scope=@deephaven/embed-chart --stream", "start:embed-grid": "lerna run start --scope=@deephaven/embed-grid --stream", + "start:embed-widget": "lerna run start --scope=@deephaven/embed-widget --stream", "pretest": "npm run build:necessary", "test": "jest --watch --changedSince origin/main", "test:debug": "node --inspect-brk node_modules/.bin/jest --config jest.config.unit.cjs --runInBand --watch", @@ -162,6 +164,7 @@ "@deephaven/dashboard-core-plugins": "file:packages/dashboard-core-plugins", "@deephaven/embed-chart": "file:packages/embed-chart", "@deephaven/embed-grid": "file:packages/embed-grid", + "@deephaven/embed-widget": "file:packages/embed-widget", "@deephaven/file-explorer": "file:packages/file-explorer", "@deephaven/filters": "file:packages/filters", "@deephaven/golden-layout": "file:packages/golden-layout", diff --git a/packages/code-studio/README.md b/packages/code-studio/README.md index fad9032098..21aeee4745 100644 --- a/packages/code-studio/README.md +++ b/packages/code-studio/README.md @@ -11,7 +11,7 @@ To start the Code Studio, run `npm install` and `npm start` in the root director Project specific settings are stored in the `.env` file. There is also an `.env.development` file which is only loaded in development builds, and `.env.development.local` which is only for local builds. For local development, you should be modifying `.env.development.local`. -For more information on `.env`, see https://create-react-app.dev/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env +For more information on `.env`, see [Vite docs](https://vitejs.dev/guide/env-and-mode.html). Below are some of the common properties which are configurable in the `.env` file. ### VITE_CORE_API_URL @@ -88,30 +88,6 @@ You can learn more in the [Vite documentation](https://vitejs.dev/guide/). To learn React, check out the [React documentation](https://reactjs.org/). -### Code Splitting - -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting - -### Analyzing the Bundle Size - -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size - -### Making a Progressive Web App - -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app - -### Advanced Configuration - -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration - -### Deployment - -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment - -### `yarn build` fails to minify - -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify - # Legal Notices Deephaven Data Labs and any contributors grant you a license to the content of this repository under the Apache 2.0 License, see the [LICENSE](../../LICENSE) file. diff --git a/packages/code-studio/index.html b/packages/code-studio/index.html index 33d8820bf3..d947e5f539 100644 --- a/packages/code-studio/index.html +++ b/packages/code-studio/index.html @@ -1,4 +1,4 @@ - + @@ -9,10 +9,6 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - Deephaven diff --git a/packages/code-studio/src/styleguide/index.html b/packages/code-studio/src/styleguide/index.html index 725e719aef..e9a7c8a76c 100644 --- a/packages/code-studio/src/styleguide/index.html +++ b/packages/code-studio/src/styleguide/index.html @@ -9,10 +9,6 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - Deephaven Styleguide diff --git a/packages/components/src/ErrorBoundary.test.tsx b/packages/components/src/ErrorBoundary.test.tsx new file mode 100644 index 0000000000..f51a184258 --- /dev/null +++ b/packages/components/src/ErrorBoundary.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import ErrorBoundary, { ErrorBoundaryProps } from './ErrorBoundary'; + +function ThrowComponent(): JSX.Element { + throw new Error('Test error'); +} + +function makeWrapper({ + children = 'Hello World', + className, + onError = jest.fn(), + fallback, +}: Partial = {}) { + return render( + + {children} + + ); +} + +it('should render the children if there is no error', () => { + const onError = jest.fn(); + const { getByText } = makeWrapper({ onError }); + expect(getByText('Hello World')).toBeInTheDocument(); + expect(onError).not.toHaveBeenCalled(); +}); + +it('should render the fallback if there is an error', () => { + const onError = jest.fn(); + const error = new Error('Test error'); + const { getByText } = makeWrapper({ + children: , + fallback:
Fallback
, + onError, + }); + expect(getByText('Fallback')).toBeInTheDocument(); + expect(onError).toHaveBeenCalledWith(error, expect.anything()); +}); diff --git a/packages/components/src/ErrorBoundary.tsx b/packages/components/src/ErrorBoundary.tsx new file mode 100644 index 0000000000..cbf803cf08 --- /dev/null +++ b/packages/components/src/ErrorBoundary.tsx @@ -0,0 +1,70 @@ +import Log from '@deephaven/log'; +import React, { Component, ReactNode } from 'react'; +import LoadingOverlay from './LoadingOverlay'; + +const log = Log.module('ErrorBoundary'); + +export interface ErrorBoundaryProps { + /** Children to catch errors from */ + children: ReactNode; + + /** Classname to wrap the error message with */ + className?: string; + + /** Callback for when an error occurs */ + onError?: (error: Error, errorInfo: React.ErrorInfo) => void; + + /** Custom fallback element */ + fallback?: ReactNode; +} + +export interface ErrorBoundaryState { + error?: Error; +} + +/** + * Error boundary for catching render errors in React. Displays an error message if an error is caught by default, or you can specify a fallback component to render. + * https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary + */ +export class ErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { error }; + } + + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { error: undefined }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + const { onError } = this.props; + log.error('Error caught by ErrorBoundary', error, errorInfo); + onError?.(error, errorInfo); + } + + render(): ReactNode { + const { children, className, fallback } = this.props; + const { error } = this.state; + if (error != null) { + if (fallback != null) { + return fallback; + } + + return ( +
+ +
+ ); + } + return children; + } +} + +export default ErrorBoundary; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index b46643e362..ef7168e802 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -22,6 +22,7 @@ export { default as DraggableItemList } from './DraggableItemList'; export * from './DraggableItemList'; export { default as DragUtils } from './DragUtils'; export { default as EditableItemList } from './EditableItemList'; +export * from './ErrorBoundary'; export { default as HierarchicalCheckboxMenu } from './HierarchicalCheckboxMenu'; export * from './HierarchicalCheckboxMenu'; export * from './ItemList'; diff --git a/packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx b/packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx index 5dfbe3974f..7f955d984a 100644 --- a/packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx +++ b/packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx @@ -139,7 +139,7 @@ export const ChartPanelPlugin = forwardRef( chartTheme, connection, metadata as ChartPanelMetadata, - fetch as unknown as () => Promise
, + fetch as () => Promise
, panelState ); }, diff --git a/packages/dashboard-core-plugins/src/GridPanelPlugin.tsx b/packages/dashboard-core-plugins/src/GridPanelPlugin.tsx index 75666bd064..0f7b2ece97 100644 --- a/packages/dashboard-core-plugins/src/GridPanelPlugin.tsx +++ b/packages/dashboard-core-plugins/src/GridPanelPlugin.tsx @@ -10,7 +10,7 @@ export const GridPanelPlugin = forwardRef( (props: WidgetPanelProps, ref: React.Ref) => { const { localDashboardId, fetch } = props; const hydratedProps = useHydrateGrid( - fetch as unknown as () => Promise, + fetch as () => Promise
, localDashboardId ); diff --git a/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx b/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx index 520130cb56..ee41291a7c 100644 --- a/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx +++ b/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx @@ -8,7 +8,7 @@ export const PandasPanelPlugin = forwardRef( (props: WidgetPanelProps, ref: React.Ref) => { const { localDashboardId, fetch } = props; const hydratedProps = useHydrateGrid( - fetch as unknown as () => Promise
, + fetch as () => Promise
, localDashboardId ); diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 65f5bd1915..c15c332397 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -24,7 +24,6 @@ "dependencies": { "@deephaven/components": "file:../components", "@deephaven/golden-layout": "file:../golden-layout", - "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/log": "file:../log", "@deephaven/react-hooks": "file:../react-hooks", "@deephaven/redux": "file:../redux", diff --git a/packages/dashboard/tsconfig.json b/packages/dashboard/tsconfig.json index 8aae239da7..0095b98b5b 100644 --- a/packages/dashboard/tsconfig.json +++ b/packages/dashboard/tsconfig.json @@ -9,7 +9,6 @@ "references": [ { "path": "../components" }, { "path": "../golden-layout" }, - { "path": "../jsapi-bootstrap" }, { "path": "../log" }, { "path": "../react-hooks" }, { "path": "../redux" }, diff --git a/packages/embed-chart/README.md b/packages/embed-chart/README.md index 2613f2870b..af176c765e 100644 --- a/packages/embed-chart/README.md +++ b/packages/embed-chart/README.md @@ -1,12 +1,10 @@ # Embedded Deephaven Chart -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). It is to provide an example React application connecting to Deephaven and displaying a chart or plot. +This project uses [Vite](https://vitejs.dev/guide/). It is to provide an example React application connecting to Deephaven and displaying a chart or plot. -## Getting Started +## Running -1. **Start the server**: Following instructions on GitHub to run deephaven-core with python: https://github.com/deephaven/deephaven-core/#run-deephaven. -2. **Install dependencies**: Run `npm install` to install all dependencies required. -3. **Start the UI**: Run `npm start` to start up the UI. It should automatically open up at http://localhost:4020. +To start the Embed Chart server, run `npm install` and `npm start` in the root directory of this repository. See the [Getting Started](../../README.md#getting-started) section for more details. ## Query Parameters @@ -22,47 +20,4 @@ Once Deephaven is running, you can open a chart with a specific name by adding t ### Configuring Server Address -By default, this project assumes you are hosting Deephaven with Python on the default port at http://localhost:10000. If Deephaven is running on a different port/server, update the `VITE_CORE_API_URL` environment variable to point to the correct server. See [.env](./.env) file for the default definition, and [create-react-app docs](https://create-react-app.dev/docs/adding-custom-environment-variables/) for other ways to set this environment variable. - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:4020](http://localhost:4020) to view it in the browser. - -The page will reload if you make edits.\ -You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). +By default, this project assumes you are hosting Deephaven with Python on the default port at http://localhost:10000. If Deephaven is running on a different port/server, update the `VITE_CORE_API_URL` environment variable to point to the correct server. See [.env](./.env) file for the default definition, and [vite docs](https://vitejs.dev/guide/env-and-mode.html#env-variables-and-modes) for other ways to set this environment variable. diff --git a/packages/embed-chart/index.html b/packages/embed-chart/index.html index ecf900e024..f39c1eb28e 100644 --- a/packages/embed-chart/index.html +++ b/packages/embed-chart/index.html @@ -1,4 +1,4 @@ - + @@ -9,10 +9,6 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - diff --git a/packages/embed-chart/src/App.scss b/packages/embed-chart/src/App.scss index 0d47f8ce25..3b73d2890b 100644 --- a/packages/embed-chart/src/App.scss +++ b/packages/embed-chart/src/App.scss @@ -9,6 +9,4 @@ bottom: 0; height: 100%; width: 100%; - background: $black; - color: $white; } diff --git a/packages/embed-chart/src/App.tsx b/packages/embed-chart/src/App.tsx index f7ee926cad..8effb551d2 100644 --- a/packages/embed-chart/src/App.tsx +++ b/packages/embed-chart/src/App.tsx @@ -42,7 +42,7 @@ async function loadFigure( * E.g. http://localhost:3000/?name=myFigure will attempt to open a figure `myFigure` * If no query param is provided, it will display an error. * By default, tries to connect to the server defined in the VITE_CORE_API_URL variable, which is set to http://localhost:10000/jsapi - * See create-react-app docs for how to update these env vars: https://create-react-app.dev/docs/adding-custom-environment-variables/ + * See Vite docs for how to update these env vars: https://vitejs.dev/guide/env-and-mode.html */ function App(): JSX.Element { const [model, setModel] = useState(); diff --git a/packages/embed-chart/src/logo.svg b/packages/embed-chart/src/logo.svg deleted file mode 100644 index 9dfc1c058c..0000000000 --- a/packages/embed-chart/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/embed-grid/README.md b/packages/embed-grid/README.md index 590041bc73..7f4b4f68ce 100644 --- a/packages/embed-grid/README.md +++ b/packages/embed-grid/README.md @@ -2,11 +2,9 @@ This project uses [Vite](https://vitejs.dev/). It is to provide an example React application connecting to Deephaven and displaying a table of data. -## Getting Started +## Running -1. **Start the server**: Following instructions on GitHub to run deephaven-core with python: https://github.com/deephaven/deephaven-core/#run-deephaven. -2. **Install dependencies**: Run `npm install` to install all dependencies required. -3. **Start the UI**: Run `npm start` to start up the UI. It should automatically open up at http://localhost:4010. +To start the Embed Grid server, run `npm install` and `npm start` in the root directory of this repository. See the [Getting Started](../../README.md#getting-started) section for more details. ## Query Parameters @@ -57,46 +55,3 @@ Once Deephaven is running, you can open a table with a specific name by adding t ### Configuring Server Address By default, this project assumes you are hosting Deephaven with Python on the default port at http://localhost:10000. If Deephaven is running on a different port/server, update the `VITE_CORE_API_URL` environment variable to point to the correct server. See [.env](./.env) file for the default definition, and [Vite docs](https://vitejs.dev/guide/env-and-mode.html) for other info about environment variables. - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:4010](http://localhost:4010) to view it in the browser. - -The page will reload if you make edits.\ -You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Vite documentation](https://vitejs.dev/guide/). - -To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/packages/embed-grid/index.html b/packages/embed-grid/index.html index 544c890086..65febadda6 100644 --- a/packages/embed-grid/index.html +++ b/packages/embed-grid/index.html @@ -1,4 +1,4 @@ - + @@ -9,12 +9,7 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - - Deephaven Embedded Grid diff --git a/packages/embed-grid/src/App.scss b/packages/embed-grid/src/App.scss index 0d47f8ce25..3b73d2890b 100644 --- a/packages/embed-grid/src/App.scss +++ b/packages/embed-grid/src/App.scss @@ -9,6 +9,4 @@ bottom: 0; height: 100%; width: 100%; - background: $black; - color: $white; } diff --git a/packages/embed-grid/src/App.tsx b/packages/embed-grid/src/App.tsx index 6246ca57c0..c3a41fcd83 100644 --- a/packages/embed-grid/src/App.tsx +++ b/packages/embed-grid/src/App.tsx @@ -67,7 +67,7 @@ async function loadTable( * E.g. http://localhost:3000/?name=myTable will attempt to open a table `myTable` * If no query param is provided, it will attempt to open a new session and create a basic time table and display that. * By default, tries to connect to the server defined in the VITE_CORE_API_URL variable, which is set to http://localhost:1000/jsapi - * See create-react-app docs for how to update these env vars: https://create-react-app.dev/docs/adding-custom-environment-variables/ + * See Vite docs for how to update these env vars: https://vitejs.dev/guide/env-and-mode.html */ function App(): JSX.Element { const connection = useConnection(); diff --git a/packages/embed-grid/src/logo.svg b/packages/embed-grid/src/logo.svg deleted file mode 100644 index 9dfc1c058c..0000000000 --- a/packages/embed-grid/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/embed-widget/.env b/packages/embed-widget/.env new file mode 100644 index 0000000000..9f830f9456 --- /dev/null +++ b/packages/embed-widget/.env @@ -0,0 +1,10 @@ +# See the values in [code sudio](../code-studio/.env) for more details +BASE_URL=./ +# We assume embed-chart is served at a nested path, e.g. '/iframe/chart' +VITE_CORE_API_URL=../../jsapi +VITE_MODULE_PLUGINS_URL=/js-plugins +VITE_CORE_API_NAME=dh-core.js +VITE_BUILD_PATH=./build +VITE_LOG_LEVEL=2 +VITE_FAVICON=/favicon-cc-app.svg +VITE_PROXY_URL=http://localhost:10000 \ No newline at end of file diff --git a/packages/embed-widget/.env.development b/packages/embed-widget/.env.development new file mode 100644 index 0000000000..9d81559403 --- /dev/null +++ b/packages/embed-widget/.env.development @@ -0,0 +1,7 @@ +BASE_URL=/iframe/widget/ +VITE_LOG_LEVEL=4 +VITE_ENABLE_LOG_PROXY=false +VITE_FAVICON=./favicon-cc-app-dev.svg + +# 4030 So not to conflict with code-studio +PORT=4030 diff --git a/packages/embed-widget/.prettierignore b/packages/embed-widget/.prettierignore new file mode 100644 index 0000000000..7ab6215869 --- /dev/null +++ b/packages/embed-widget/.prettierignore @@ -0,0 +1 @@ +./public/__mocks__/dh-core.js diff --git a/packages/embed-widget/README.md b/packages/embed-widget/README.md new file mode 100644 index 0000000000..ba79e1e974 --- /dev/null +++ b/packages/embed-widget/README.md @@ -0,0 +1,23 @@ +# Embedded Deephaven Widget + +This project uses [Vite](https://vitejs.dev/guide/). It is to provide an example React application connecting to Deephaven and displaying a widget. + +## Running + +To start the Embed Widget server, run `npm install` and `npm start` in the root directory of this repository. See the [Getting Started](../../README.md#getting-started) section for more details. + +## Query Parameters + +- `name`: Required. The name of the widget to load + +## Advanced + +### Application Mode + +See the guide for how to set up core in Application Mode: https://deephaven.io/core/docs/how-to-guides/application-mode/ + +Once Deephaven is running, you can open a widget with a specific name by adding the query param `name`, e.g. http://localhost:4030/?name=world + +### Configuring Server Address + +By default, this project assumes you are hosting Deephaven with Python on the default port at http://localhost:10000. If Deephaven is running on a different port/server, update the `VITE_CORE_API_URL` environment variable to point to the correct server. See [.env](./.env) file for the default definition, and [vite docs](https://vitejs.dev/guide/env-and-mode.html#env-variables-and-modes) for other ways to set this environment variable. diff --git a/packages/embed-widget/index.html b/packages/embed-widget/index.html new file mode 100644 index 0000000000..1a0570fd44 --- /dev/null +++ b/packages/embed-widget/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + + Deephaven Embedded Widget + + + +
+ + + diff --git a/packages/embed-widget/jest.config.cjs b/packages/embed-widget/jest.config.cjs new file mode 100644 index 0000000000..6ee7d9fc30 --- /dev/null +++ b/packages/embed-widget/jest.config.cjs @@ -0,0 +1,8 @@ +const baseConfig = require('../../jest.config.base.cjs'); +const packageJson = require('./package'); + +module.exports = { + ...baseConfig, + displayName: packageJson.name, + resetMocks: false, +}; diff --git a/packages/embed-widget/licenses.txt b/packages/embed-widget/licenses.txt new file mode 100644 index 0000000000..b0a7645f89 --- /dev/null +++ b/packages/embed-widget/licenses.txt @@ -0,0 +1,22 @@ +├─ @deephaven/code-studio@0.0.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/deephaven/web-client-ui +├─ @deephaven/components@0.0.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/deephaven/web-client-ui +├─ @deephaven/grid@0.0.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/deephaven/web-client-ui +├─ @deephaven/icons@0.0.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/deephaven/web-client-ui +├─ @deephaven/jsapi-shim@0.0.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/deephaven/web-client-ui +├─ @deephaven/log@0.0.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/deephaven/web-client-ui +└─ @deephaven/utils@0.0.1 + ├─ licenses: Apache-2.0 + └─ repository: https://github.com/deephaven/web-client-ui + diff --git a/packages/embed-widget/package.json b/packages/embed-widget/package.json new file mode 100644 index 0000000000..069e503ff5 --- /dev/null +++ b/packages/embed-widget/package.json @@ -0,0 +1,47 @@ +{ + "name": "@deephaven/embed-widget", + "version": "0.55.0", + "description": "Deephaven Embedded Widget", + "author": "Deephaven Data Labs LLC", + "license": "Apache-2.0", + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/deephaven/web-client-ui.git", + "directory": "packages/embed-widget" + }, + "homepage": ".", + "main": "public/index.js", + "files": [ + "build" + ], + "dependencies": { + "@deephaven/app-utils": "file:../app-utils", + "@deephaven/components": "file:../components", + "@deephaven/dashboard-core-plugins": "file:../dashboard-core-plugins", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", + "@deephaven/jsapi-components": "file:../jsapi-components", + "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/jsapi-utils": "file:../jsapi-utils", + "@deephaven/log": "file:../log", + "@deephaven/plugin": "file:../plugin", + "fira": "mozilla/fira#4.202", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "scripts": { + "analyze": "source-map-explorer build/assets/*.js --no-border-checks", + "start": "vite", + "build": "NODE_OPTIONS='--max-old-space-size=4096' vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@deephaven/eslint-config": "file:../eslint-config", + "@deephaven/mocks": "file:../mocks", + "@deephaven/prettier-config": "file:../prettier-config", + "@deephaven/stylelint-config": "file:../stylelint-config" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/embed-widget/public/favicon-cc-app-dev.svg b/packages/embed-widget/public/favicon-cc-app-dev.svg new file mode 100644 index 0000000000..502e4d7e13 --- /dev/null +++ b/packages/embed-widget/public/favicon-cc-app-dev.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/embed-widget/public/favicon-cc-app.svg b/packages/embed-widget/public/favicon-cc-app.svg new file mode 100644 index 0000000000..bceaf06fb3 --- /dev/null +++ b/packages/embed-widget/public/favicon-cc-app.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/embed-widget/public/logo.png b/packages/embed-widget/public/logo.png new file mode 100644 index 0000000000..ac131b6d40 Binary files /dev/null and b/packages/embed-widget/public/logo.png differ diff --git a/packages/embed-widget/public/manifest.json b/packages/embed-widget/public/manifest.json new file mode 100644 index 0000000000..ad59db692e --- /dev/null +++ b/packages/embed-widget/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Deephaven", + "name": "Deephaven Community App", + "icons": [ + { + "src": "favicon-cc-app.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#2d2a2e", + "background_color": "#2d2a2e" +} diff --git a/packages/embed-widget/src/App.scss b/packages/embed-widget/src/App.scss new file mode 100644 index 0000000000..3b73d2890b --- /dev/null +++ b/packages/embed-widget/src/App.scss @@ -0,0 +1,12 @@ +// Import Deephaven styling so we can use it's variables +@import '@deephaven/components/scss/custom.scss'; + +.App { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100%; + width: 100%; +} diff --git a/packages/embed-widget/src/App.tsx b/packages/embed-widget/src/App.tsx new file mode 100644 index 0000000000..e0cf82c3b1 --- /dev/null +++ b/packages/embed-widget/src/App.tsx @@ -0,0 +1,91 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { + ContextMenuRoot, + ErrorBoundary, + LoadingOverlay, +} from '@deephaven/components'; // Use the loading spinner from the Deephaven components package +import { useConnection } from '@deephaven/jsapi-components'; +import type { VariableDefinition } from '@deephaven/jsapi-types'; +import { fetchVariableDefinition } from '@deephaven/jsapi-utils'; +import Log from '@deephaven/log'; +import { WidgetView } from '@deephaven/plugin'; +import './App.scss'; // Styles for in this app + +const log = Log.module('EmbedWidget.App'); + +/** + * A functional React component that displays a Deephaven Widget using the @deephaven/plugin package. + * It will attempt to open and display the widget specified with the `name` parameter, expecting it to be present on the server. + * E.g. http://localhost:4030/?name=myWidget will attempt to open a widget `myWidget` + * If no query param is provided, it will display an error. + * By default, tries to connect to the server defined in the VITE_CORE_API_URL variable, which is set to http://localhost:10000/jsapi + * See Vite docs for how to update these env vars: https://vitejs.dev/guide/env-and-mode.html + */ +function App(): JSX.Element { + const [error, setError] = useState(); + const [definition, setDefinition] = useState(); + const searchParams = useMemo( + () => new URLSearchParams(window.location.search), + [] + ); + // Get the widget name from the query param `name`. + const name = searchParams.get('name'); + const connection = useConnection(); + + useEffect( + function initializeApp() { + async function initApp(): Promise { + try { + if (name == null) { + throw new Error('Missing URL parameter "name"'); + } + + log.debug(`Loading widget definition for ${name}...`); + + const newDefinition = await fetchVariableDefinition(connection, name); + + setDefinition(newDefinition); + + log.debug(`Widget definition successfully loaded for ${name}`); + } catch (e: unknown) { + log.error(`Unable to load widget definition for ${name}`, e); + setError(`${e}`); + } + } + initApp(); + }, + [connection, name] + ); + + const isLoaded = definition != null && error == null; + const isLoading = definition == null && error == null; + + const fetch = useMemo(() => { + if (definition == null) { + return async () => { + throw new Error('Definition is null'); + }; + } + return () => connection.getObject(definition); + }, [connection, definition]); + + return ( +
+ {isLoaded && ( + + + + )} + {!isLoaded && ( + + )} + +
+ ); +} + +export default App; diff --git a/packages/embed-widget/src/index.scss b/packages/embed-widget/src/index.scss new file mode 100644 index 0000000000..d2d53d7593 --- /dev/null +++ b/packages/embed-widget/src/index.scss @@ -0,0 +1,14 @@ +@import '@deephaven/components/scss/custom.scss'; + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/packages/embed-widget/src/index.tsx b/packages/embed-widget/src/index.tsx new file mode 100644 index 0000000000..78ce51dbeb --- /dev/null +++ b/packages/embed-widget/src/index.tsx @@ -0,0 +1,53 @@ +import React, { Suspense } from 'react'; +import ReactDOM from 'react-dom'; +import '@deephaven/components/scss/BaseStyleSheet.scss'; +import { LoadingOverlay, preloadTheme } from '@deephaven/components'; +import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; +import './index.scss'; + +preloadTheme(); + +// Lazy load components for code splitting and also to avoid importing the jsapi-shim before API is bootstrapped. +// eslint-disable-next-line react-refresh/only-export-components +const App = React.lazy(() => import('./App')); + +// eslint-disable-next-line react-refresh/only-export-components +const AppBootstrap = React.lazy(async () => { + const module = await import('@deephaven/app-utils'); + return { default: module.AppBootstrap }; +}); + +const apiURL = new URL( + `${import.meta.env.VITE_CORE_API_URL}/${import.meta.env.VITE_CORE_API_NAME}`, + document.baseURI +); + +const pluginsURL = new URL( + import.meta.env.VITE_MODULE_PLUGINS_URL, + document.baseURI +); + +// Lazy load the configs because it breaks initial page loads otherwise +async function getCorePlugins() { + const dashboardCorePlugins = await import( + '@deephaven/dashboard-core-plugins' + ); + const { GridPluginConfig, PandasPluginConfig, ChartPluginConfig } = + dashboardCorePlugins; + return [GridPluginConfig, PandasPluginConfig, ChartPluginConfig]; +} + +ReactDOM.render( + + }> + + + + + , + document.getElementById('root') +); diff --git a/packages/embed-widget/src/vite-env.d.ts b/packages/embed-widget/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/embed-widget/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/embed-widget/tsconfig.json b/packages/embed-widget/tsconfig.json new file mode 100644 index 0000000000..1dec33fe39 --- /dev/null +++ b/packages/embed-widget/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src/", + "outDir": "dist/", + "noEmit": true, + "emitDeclarationOnly": false + }, + "include": ["src"], + "references": [ + { "path": "../app-utils" }, + { "path": "../components" }, + { "path": "../dashboard-core-plugins" }, + { "path": "../jsapi-bootstrap" }, + { "path": "../jsapi-components" }, + { "path": "../jsapi-types" }, + { "path": "../jsapi-utils" }, + { "path": "../log" }, + { "path": "../plugin" } + ] +} diff --git a/packages/embed-widget/vite.config.ts b/packages/embed-widget/vite.config.ts new file mode 100644 index 0000000000..f67391d79f --- /dev/null +++ b/packages/embed-widget/vite.config.ts @@ -0,0 +1,111 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react-swc'; +import path from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ''); + + const packagesDir = path.resolve(__dirname, '..'); + + let port = Number.parseInt(env.PORT, 10); + if (Number.isNaN(port) || port <= 0) { + port = 4030; + } + + const baseURL = new URL(env.BASE_URL, `http://localhost:${port}/`); + // These are paths which should be proxied to the core server + // https://vitejs.dev/config/server-options.html#server-proxy + const proxy = { + // proxy the websocket requests, allows tunneling to work with a single port + '^/arrow\\.*': { + target: env.VITE_PROXY_URL, + changeOrigin: true, + ws: true, + }, + '^/io\\.deephaven\\..*': { + target: env.VITE_PROXY_URL, + changeOrigin: true, + ws: true, + }, + }; + + // Some paths need to proxy to the engine server + // Vite does not have a "any unknown fallback to proxy" like CRA + // It is possible to add one with a custom middleware though if this list grows + if (env.VITE_PROXY_URL) { + [env.VITE_CORE_API_URL, env.VITE_MODULE_PLUGINS_URL].forEach(p => { + const route = new URL(p, baseURL).pathname; + proxy[route] = { + target: env.VITE_PROXY_URL, + changeOrigin: true, + }; + }); + } + + return { + base: './', // Vite defaults to absolute URLs, but embed-widget is an embedded deployment so all assets are relative paths + envPrefix: ['VITE_', 'npm_'], // Needed to use $npm_package_version + server: { + port, + proxy, + }, + preview: { + port, + proxy, + }, + resolve: { + alias: + mode === 'development' + ? [ + { + find: /^@deephaven\/(.*)\/scss\/(.*)/, + replacement: `${packagesDir}/$1/scss/$2`, + }, + { + find: /^@deephaven\/(?!icons)(.*)/, // Icons package can not import from src + replacement: `${packagesDir}/$1/src`, + }, + ] + : [], + }, + build: { + outDir: env.VITE_BUILD_PATH, + emptyOutDir: true, + sourcemap: true, + rollupOptions: { + output: { + manualChunks: id => { + /** + * Without this, our chunk order may cause a circular reference + * by putting the helpers in the vendor or plotly chunk + * This causes failures with loading the compiled version + * + * See https://github.com/rollup/plugins/issues/591 + */ + if (id === '\0commonjsHelpers.js') { + return 'helpers'; + } + + if (id.includes('node_modules')) { + if (id.includes('plotly.js')) { + return 'plotly'; + } + return 'vendor'; + } + }, + }, + }, + }, + optimizeDeps: { + esbuildOptions: { + // Some packages need this to start properly if they reference global + define: { + global: 'globalThis', + }, + }, + }, + plugins: [react()], + }; +}); diff --git a/packages/plugin/src/PluginTypes.ts b/packages/plugin/src/PluginTypes.ts index 15ea49f05a..1588a60259 100644 --- a/packages/plugin/src/PluginTypes.ts +++ b/packages/plugin/src/PluginTypes.ts @@ -1,5 +1,4 @@ import type { BaseThemeType } from '@deephaven/components'; -import { type Widget } from '@deephaven/jsapi-types'; import { type EventEmitter, type ItemContainer, @@ -107,7 +106,7 @@ export function isDashboardPlugin( } export interface WidgetComponentProps { - fetch: () => Promise; + fetch: () => Promise; } export interface WidgetPanelProps extends WidgetComponentProps { diff --git a/packages/plugin/src/WidgetView.tsx b/packages/plugin/src/WidgetView.tsx new file mode 100644 index 0000000000..6e92c17ff7 --- /dev/null +++ b/packages/plugin/src/WidgetView.tsx @@ -0,0 +1,31 @@ +import React, { useMemo } from 'react'; +import usePlugins from './usePlugins'; +import { isWidgetPlugin } from './PluginTypes'; + +export type WidgetViewProps = { + /** Fetch function to return the widget */ + fetch: () => Promise; + + /** Type of the widget */ + type: string; +}; + +export function WidgetView({ fetch, type }: WidgetViewProps): JSX.Element { + const plugins = usePlugins(); + const plugin = useMemo( + () => + [...plugins.values()] + .filter(isWidgetPlugin) + .find(p => [p.supportedTypes].flat().includes(type)), + [plugins, type] + ); + + if (plugin != null) { + const Component = plugin.component; + return ; + } + + throw new Error(`Unknown widget type '${type}'`); +} + +export default WidgetView; diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index b79658c8e8..ff8cc4056b 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -3,3 +3,4 @@ export * from './PluginTypes'; export * from './PluginUtils'; export * from './TablePlugin'; export * from './usePlugins'; +export * from './WidgetView'; diff --git a/tsconfig.json b/tsconfig.json index a7a7528166..133185b4d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "references": [ { "path": "./packages/code-studio" }, { "path": "./packages/embed-chart" }, - { "path": "./packages/embed-grid" } + { "path": "./packages/embed-grid" }, + { "path": "./packages/embed-widget" } ] }