diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 494d52da8d..19ffafa4d7 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -43,7 +43,7 @@ jobs: run: npm ci --no-audit - name: Build - run: VITE_CORE_API_URL=http://localhost:10000/jsapi npm run build + run: npm run build - name: Install Playwright dependencies run: PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install --with-deps diff --git a/package-lock.json b/package-lock.json index cb3224b722..dcf0030f37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,8 +27,10 @@ "@deephaven/grid": "file:packages/grid", "@deephaven/icons": "file:packages/icons", "@deephaven/iris-grid": "file:packages/iris-grid", + "@deephaven/jsapi-bootstrap": "file:packages/jsapi-bootstrap", "@deephaven/jsapi-components": "file:packages/jsapi-components", "@deephaven/jsapi-shim": "file:packages/jsapi-shim", + "@deephaven/jsapi-types": "file:packages/jsapi-types", "@deephaven/jsapi-utils": "file:packages/jsapi-utils", "@deephaven/log": "file:packages/log", "@deephaven/mocks": "file:packages/mocks", @@ -2014,6 +2016,10 @@ "resolved": "packages/iris-grid", "link": true }, + "node_modules/@deephaven/jsapi-bootstrap": { + "resolved": "packages/jsapi-bootstrap", + "link": true + }, "node_modules/@deephaven/jsapi-components": { "resolved": "packages/jsapi-components", "link": true @@ -2022,6 +2028,10 @@ "resolved": "packages/jsapi-shim", "link": true }, + "node_modules/@deephaven/jsapi-types": { + "resolved": "packages/jsapi-types", + "link": true + }, "node_modules/@deephaven/jsapi-utils": { "resolved": "packages/jsapi-utils", "link": true @@ -25257,7 +25267,9 @@ "@deephaven/grid": "file:../grid", "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", + "@deephaven/jsapi-types": "file:../jsapi-types", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", "@deephaven/pouch-storage": "file:../pouch-storage", @@ -25540,6 +25552,7 @@ "dependencies": { "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", @@ -25595,6 +25608,7 @@ "dependencies": { "@deephaven/components": "file:../components", "@deephaven/iris-grid": "file:../iris-grid", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", @@ -25804,6 +25818,25 @@ "react-dom": "^17.x" } }, + "packages/jsapi-bootstrap": { + "name": "@deephaven/jsapi-bootstrap", + "version": "0.29.0", + "license": "Apache-2.0", + "dependencies": { + "@deephaven/components": "file:../components", + "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/log": "file:../log" + }, + "devDependencies": { + "@deephaven/tsconfig": "file:../tsconfig" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": "^17.x" + } + }, "packages/jsapi-components": { "name": "@deephaven/jsapi-components", "version": "0.29.0", @@ -25834,6 +25867,7 @@ "version": "0.29.0", "license": "Apache-2.0", "dependencies": { + "@deephaven/jsapi-types": "file:../jsapi-types", "prop-types": "^15.7.2" }, "devDependencies": { @@ -25843,6 +25877,17 @@ "node": ">=16" } }, + "packages/jsapi-types": { + "name": "@deephaven/jsapi-types", + "version": "0.29.0", + "license": "Apache-2.0", + "devDependencies": { + "@deephaven/tsconfig": "file:../tsconfig" + }, + "engines": { + "node": ">=16" + } + }, "packages/jsapi-utils": { "name": "@deephaven/jsapi-utils", "version": "0.29.0", @@ -27228,7 +27273,9 @@ "@deephaven/grid": "file:../grid", "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", + "@deephaven/jsapi-types": "file:../jsapi-types", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", "@deephaven/mocks": "file:../mocks", @@ -27432,6 +27479,7 @@ "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", "@deephaven/eslint-config": "file:../eslint-config", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", @@ -27474,6 +27522,7 @@ "@deephaven/components": "file:../components", "@deephaven/eslint-config": "file:../eslint-config", "@deephaven/iris-grid": "file:../iris-grid", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", @@ -27609,6 +27658,15 @@ "web-streams-polyfill": "^2.1.0" } }, + "@deephaven/jsapi-bootstrap": { + "version": "file:packages/jsapi-bootstrap", + "requires": { + "@deephaven/components": "file:../components", + "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/log": "file:../log", + "@deephaven/tsconfig": "file:../tsconfig" + } + }, "@deephaven/jsapi-components": { "version": "file:packages/jsapi-components", "requires": { @@ -27627,10 +27685,17 @@ "@deephaven/jsapi-shim": { "version": "file:packages/jsapi-shim", "requires": { + "@deephaven/jsapi-types": "file:../jsapi-types", "@deephaven/tsconfig": "file:../tsconfig", "prop-types": "^15.7.2" } }, + "@deephaven/jsapi-types": { + "version": "file:packages/jsapi-types", + "requires": { + "@deephaven/tsconfig": "file:../tsconfig" + } + }, "@deephaven/jsapi-utils": { "version": "file:packages/jsapi-utils", "requires": { diff --git a/package.json b/package.json index b58a01793b..78182c676f 100644 --- a/package.json +++ b/package.json @@ -161,8 +161,10 @@ "@deephaven/grid": "file:packages/grid", "@deephaven/icons": "file:packages/icons", "@deephaven/iris-grid": "file:packages/iris-grid", + "@deephaven/jsapi-bootstrap": "file:packages/jsapi-bootstrap", "@deephaven/jsapi-components": "file:packages/jsapi-components", "@deephaven/jsapi-shim": "file:packages/jsapi-shim", + "@deephaven/jsapi-types": "file:packages/jsapi-types", "@deephaven/jsapi-utils": "file:packages/jsapi-utils", "@deephaven/log": "file:packages/log", "@deephaven/mocks": "file:packages/mocks", diff --git a/packages/code-studio/index.html b/packages/code-studio/index.html index 256234f1e7..8fb801d0da 100644 --- a/packages/code-studio/index.html +++ b/packages/code-studio/index.html @@ -13,40 +13,7 @@ homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ --> - - - - - - Deephaven diff --git a/packages/code-studio/package.json b/packages/code-studio/package.json index 41a894abf4..c275308eb8 100644 --- a/packages/code-studio/package.json +++ b/packages/code-studio/package.json @@ -22,7 +22,9 @@ "@deephaven/grid": "file:../grid", "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", + "@deephaven/jsapi-types": "file:../jsapi-types", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", "@deephaven/pouch-storage": "file:../pouch-storage", diff --git a/packages/code-studio/src/AppRoot.tsx b/packages/code-studio/src/AppRoot.tsx new file mode 100644 index 0000000000..2b074d184e --- /dev/null +++ b/packages/code-studio/src/AppRoot.tsx @@ -0,0 +1,29 @@ +import React, { useEffect } from 'react'; +import { Provider } from 'react-redux'; +import { MonacoUtils } from '@deephaven/console'; +import { store } from '@deephaven/redux'; +import MonacoWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; +import AppRouter from './main/AppRouter'; +import DownloadServiceWorkerUtils from './DownloadServiceWorkerUtils'; +import { unregister } from './serviceWorker'; + +export function AppRoot() { + useEffect(() => { + unregister(); + DownloadServiceWorkerUtils.registerOnLoaded(); + MonacoUtils.init({ getWorker: () => new MonacoWorker() }); + + // disable annoying dnd-react warnings + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window['__react-beautiful-dnd-disable-dev-warnings'] = true; + }, []); + + return ( + + + + ); +} + +export default AppRoot; diff --git a/packages/code-studio/src/index.tsx b/packages/code-studio/src/index.tsx index 5391856bac..0988791298 100644 --- a/packages/code-studio/src/index.tsx +++ b/packages/code-studio/src/index.tsx @@ -1,29 +1,25 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; import 'fira'; import '@deephaven/components/scss/BaseStyleSheet.scss'; -import { MonacoUtils } from '@deephaven/console'; -import { store } from '@deephaven/redux'; -import MonacoWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; -import AppRouter from './main/AppRouter'; -import DownloadServiceWorkerUtils from './DownloadServiceWorkerUtils'; +import { LoadingOverlay } from '@deephaven/components'; +import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import logInit from './log/LogInit'; -import { unregister } from './serviceWorker'; logInit(); +const AppRoot = React.lazy(() => import('./AppRoot')); + ReactDOM.render( - - - , + + }> + + + , document.getElementById('root') ); -unregister(); -DownloadServiceWorkerUtils.registerOnLoaded(); -MonacoUtils.init({ getWorker: () => new MonacoWorker() }); - -// disable annoying dnd-react warnings -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -window['__react-beautiful-dnd-disable-dev-warnings'] = true; diff --git a/packages/code-studio/tsconfig.json b/packages/code-studio/tsconfig.json index b397731531..c531a71b4c 100644 --- a/packages/code-studio/tsconfig.json +++ b/packages/code-studio/tsconfig.json @@ -6,17 +6,8 @@ "noEmit": true, "emitDeclarationOnly": false }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx" - ], - "exclude": [ - "node_modules", - "src/**/*.test.*", - "src/**/__mocks__/*" - ], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], + "exclude": ["node_modules", "src/**/*.test.*", "src/**/__mocks__/*"], "references": [ { "path": "../chart" @@ -45,9 +36,18 @@ { "path": "../iris-grid" }, + { + "path": "../jsapi-bootstrap" + }, { "path": "../jsapi-shim" }, + { + "path": "../jsapi-types" + }, + { + "path": "../jsapi-utils" + }, { "path": "../log" }, @@ -70,4 +70,4 @@ "path": "../filters" } ] -} \ No newline at end of file +} diff --git a/packages/code-studio/vite.config.ts b/packages/code-studio/vite.config.ts index edba8b9ca4..b225ba1adb 100644 --- a/packages/code-studio/vite.config.ts +++ b/packages/code-studio/vite.config.ts @@ -107,6 +107,7 @@ export default defineConfig(({ mode }) => { outDir: path.resolve(__dirname, env.VITE_BUILD_PATH), emptyOutDir: true, sourcemap: true, + target: 'esnext', rollupOptions: { output: { manualChunks: id => { diff --git a/packages/components/src/LoadingOverlay.tsx b/packages/components/src/LoadingOverlay.tsx index ae0dd98aa8..0c3b71dbf7 100644 --- a/packages/components/src/LoadingOverlay.tsx +++ b/packages/components/src/LoadingOverlay.tsx @@ -8,9 +8,9 @@ import LoadingSpinner from './LoadingSpinner'; import './LoadingOverlay.scss'; type LoadingOverlayProps = { - isLoaded: boolean; - isLoading: boolean; - errorMessage: string | null; + isLoaded?: boolean; + isLoading?: boolean; + errorMessage?: string | null; 'data-testid'?: string; }; diff --git a/packages/embed-chart/.env b/packages/embed-chart/.env index 4ad76c3caf..5c3d56bf62 100644 --- a/packages/embed-chart/.env +++ b/packages/embed-chart/.env @@ -2,7 +2,6 @@ # Set this value to __mocks__ to use mock server instead VITE_CORE_API_URL=/jsapi VITE_CORE_API_NAME=dh-core.js -VITE_OPEN_API_NAME=dh-internal.js VITE_BUILD_PATH=./build VITE_LOG_LEVEL=2 VITE_FAVICON=./favicon-cc-app.svg diff --git a/packages/embed-chart/.env.development b/packages/embed-chart/.env.development index f1a64b6a43..d4a436f374 100644 --- a/packages/embed-chart/.env.development +++ b/packages/embed-chart/.env.development @@ -1,6 +1,7 @@ -VITE_CORE_API_URL=http://localhost:10000/jsapi VITE_LOG_LEVEL=4 VITE_FAVICON=./favicon-cc-app-dev.svg # 4020 So not to conflict with code-studio PORT=4020 + +VITE_PROXY_URL=http://localhost:10000 \ No newline at end of file diff --git a/packages/embed-chart/index.html b/packages/embed-chart/index.html index 8b0269aa8f..89714a3f99 100644 --- a/packages/embed-chart/index.html +++ b/packages/embed-chart/index.html @@ -15,38 +15,6 @@ - - - - - Deephaven Embedded Chart diff --git a/packages/embed-chart/package.json b/packages/embed-chart/package.json index 4340185204..26f46b0640 100644 --- a/packages/embed-chart/package.json +++ b/packages/embed-chart/package.json @@ -18,6 +18,7 @@ "dependencies": { "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", diff --git a/packages/embed-chart/src/index.tsx b/packages/embed-chart/src/index.tsx index ea799aedd2..5efc61cf42 100644 --- a/packages/embed-chart/src/index.tsx +++ b/packages/embed-chart/src/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; // Fira fonts are not necessary, but look the best @@ -7,7 +7,22 @@ import 'fira'; // Need to import the base style sheet for proper styling // eslint-disable-next-line import/no-unresolved import '@deephaven/components/scss/BaseStyleSheet.scss'; +import { LoadingOverlay } from '@deephaven/components'; +import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import './index.scss'; -import App from './App'; -ReactDOM.render(, document.getElementById('root')); +const App = React.lazy(() => import('./App')); + +ReactDOM.render( + + }> + + + , + document.getElementById('root') +); diff --git a/packages/embed-chart/tsconfig.json b/packages/embed-chart/tsconfig.json index c0d0fd2fab..1fc3a596fd 100644 --- a/packages/embed-chart/tsconfig.json +++ b/packages/embed-chart/tsconfig.json @@ -6,9 +6,7 @@ "noEmit": true, "emitDeclarationOnly": false }, - "include": [ - "src" - ], + "include": ["src"], "references": [ { "path": "../chart" @@ -16,6 +14,9 @@ { "path": "../components" }, + { + "path": "../jsapi-bootstrap" + }, { "path": "../jsapi-shim" }, @@ -26,4 +27,4 @@ "path": "../log" } ] -} \ No newline at end of file +} diff --git a/packages/embed-chart/vite.config.ts b/packages/embed-chart/vite.config.ts index fc6c8b3726..6bd44abb00 100644 --- a/packages/embed-chart/vite.config.ts +++ b/packages/embed-chart/vite.config.ts @@ -22,14 +22,44 @@ export default defineConfig(({ mode }) => { port = 4020; } + // 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].forEach(p => { + proxy[p] = { + target: env.VITE_PROXY_URL, + changeOrigin: true, + }; + }); + } + return { base: './', // Vite defaults to absolute URLs, but embed-chart 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: [ diff --git a/packages/embed-grid/.env b/packages/embed-grid/.env index 4ad76c3caf..5c3d56bf62 100644 --- a/packages/embed-grid/.env +++ b/packages/embed-grid/.env @@ -2,7 +2,6 @@ # Set this value to __mocks__ to use mock server instead VITE_CORE_API_URL=/jsapi VITE_CORE_API_NAME=dh-core.js -VITE_OPEN_API_NAME=dh-internal.js VITE_BUILD_PATH=./build VITE_LOG_LEVEL=2 VITE_FAVICON=./favicon-cc-app.svg diff --git a/packages/embed-grid/.env.development b/packages/embed-grid/.env.development index 3700dd34f4..b98c681181 100644 --- a/packages/embed-grid/.env.development +++ b/packages/embed-grid/.env.development @@ -1,6 +1,7 @@ -VITE_CORE_API_URL=http://localhost:10000/jsapi VITE_LOG_LEVEL=4 VITE_FAVICON=./favicon-cc-app-dev.svg # 4010 So not to conflict with code-studio PORT=4010 + +VITE_PROXY_URL=http://localhost:10000 \ No newline at end of file diff --git a/packages/embed-grid/index.html b/packages/embed-grid/index.html index d240a24b67..63e32e4e15 100644 --- a/packages/embed-grid/index.html +++ b/packages/embed-grid/index.html @@ -15,38 +15,6 @@ - - - - - Deephaven Embedded Grid diff --git a/packages/embed-grid/package.json b/packages/embed-grid/package.json index db69e9719a..d53191a10c 100644 --- a/packages/embed-grid/package.json +++ b/packages/embed-grid/package.json @@ -18,6 +18,7 @@ "dependencies": { "@deephaven/components": "file:../components", "@deephaven/iris-grid": "file:../iris-grid", + "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", "@deephaven/jsapi-utils": "file:../jsapi-utils", "@deephaven/log": "file:../log", diff --git a/packages/embed-grid/src/index.tsx b/packages/embed-grid/src/index.tsx index ea799aedd2..5efc61cf42 100644 --- a/packages/embed-grid/src/index.tsx +++ b/packages/embed-grid/src/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; // Fira fonts are not necessary, but look the best @@ -7,7 +7,22 @@ import 'fira'; // Need to import the base style sheet for proper styling // eslint-disable-next-line import/no-unresolved import '@deephaven/components/scss/BaseStyleSheet.scss'; +import { LoadingOverlay } from '@deephaven/components'; +import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import './index.scss'; -import App from './App'; -ReactDOM.render(, document.getElementById('root')); +const App = React.lazy(() => import('./App')); + +ReactDOM.render( + + }> + + + , + document.getElementById('root') +); diff --git a/packages/embed-grid/tsconfig.json b/packages/embed-grid/tsconfig.json index 62ff8c064c..867f3a6fa8 100644 --- a/packages/embed-grid/tsconfig.json +++ b/packages/embed-grid/tsconfig.json @@ -6,9 +6,7 @@ "noEmit": true, "emitDeclarationOnly": false }, - "include": [ - "src" - ], + "include": ["src"], "references": [ { "path": "../components" @@ -16,6 +14,9 @@ { "path": "../iris-grid" }, + { + "path": "../jsapi-bootstrap" + }, { "path": "../jsapi-shim" }, @@ -26,4 +27,4 @@ "path": "../log" } ] -} \ No newline at end of file +} diff --git a/packages/embed-grid/vite.config.ts b/packages/embed-grid/vite.config.ts index 6c267470e6..405926843d 100644 --- a/packages/embed-grid/vite.config.ts +++ b/packages/embed-grid/vite.config.ts @@ -22,14 +22,44 @@ export default defineConfig(({ mode }) => { port = 4010; } + // 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].forEach(p => { + proxy[p] = { + target: env.VITE_PROXY_URL, + changeOrigin: true, + }; + }); + } + return { base: './', // Vite defaults to absolute URLs, but embed-grid 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: [ diff --git a/packages/jsapi-bootstrap/.gitignore b/packages/jsapi-bootstrap/.gitignore new file mode 100644 index 0000000000..742a2cf9d2 --- /dev/null +++ b/packages/jsapi-bootstrap/.gitignore @@ -0,0 +1,31 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build +/dist + +# misc +.vscode +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +.project +.settings/ +.eslintcache +.stylelintcache + +/public/vs + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +src/**/*.css diff --git a/packages/jsapi-bootstrap/README.md b/packages/jsapi-bootstrap/README.md new file mode 100644 index 0000000000..269df5de83 --- /dev/null +++ b/packages/jsapi-bootstrap/README.md @@ -0,0 +1,52 @@ +# @deephaven/jsapi-bootstrap + +This is a library to bootstrap load the JS API. It provides functionality to load the API and set it in a context object, or to set it globally to retain legacy behaviour. Will display an error if unable to load the API. + +## Install + +```bash +npm install --save @deephaven/jsapi-bootstrap +``` + +## Usage + +### Using Context + +```javascript +import { ApiBootstrap, useApi } from '@deephaven/jsapi-bootstrap'; + +function MyComponent() { + const api = useApi(); + + ... +} + + + +; +``` + +### Using API globally (legacy behaviour) + +If you're using the JSAPI shim or relying on the API to be set globally, you'll need to lazily load your component first so the API is set before imports attempt to use it. + +```javascript +// App.tsx +import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; + +const MyComponent = React.lazy(() => import('./MyComponent')); + + + Loading...}> + + +; + +// MyComponent.tsx +import dh from '@deephaven/jsapi-shim'; +function MyComponent() { + const client = new dh.CoreClient(...); + + ... +} +``` diff --git a/packages/jsapi-bootstrap/jest.config.cjs b/packages/jsapi-bootstrap/jest.config.cjs new file mode 100644 index 0000000000..365815a412 --- /dev/null +++ b/packages/jsapi-bootstrap/jest.config.cjs @@ -0,0 +1,7 @@ +const baseConfig = require('../../jest.config.base.cjs'); +const packageJson = require('./package'); + +module.exports = { + ...baseConfig, + displayName: packageJson.name, +}; diff --git a/packages/jsapi-bootstrap/package.json b/packages/jsapi-bootstrap/package.json new file mode 100644 index 0000000000..c76580b14a --- /dev/null +++ b/packages/jsapi-bootstrap/package.json @@ -0,0 +1,41 @@ +{ + "name": "@deephaven/jsapi-bootstrap", + "version": "0.29.0", + "description": "Deephaven JSAPI Bootstrap", + "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/jsapi-bootstrap" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "source": "src/index.ts", + "engines": { + "node": ">=16" + }, + "scripts": { + "build": "cross-env NODE_ENV=production run-p build:*", + "build:babel": "babel ./src --out-dir ./dist --extensions \".ts,.tsx,.js,.jsx\" --source-maps --root-mode upward", + "build:sass": "sass --embed-sources --load-path=../../node_modules ./src:./dist" + }, + "dependencies": { + "@deephaven/components": "file:../components", + "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/log": "file:../log" + }, + "devDependencies": { + "@deephaven/tsconfig": "file:../tsconfig" + }, + "peerDependencies": { + "react": "^17.x" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/jsapi-bootstrap/src/ApiBootstrap.tsx b/packages/jsapi-bootstrap/src/ApiBootstrap.tsx new file mode 100644 index 0000000000..5a3d4dcf0a --- /dev/null +++ b/packages/jsapi-bootstrap/src/ApiBootstrap.tsx @@ -0,0 +1,72 @@ +import React, { createContext, useEffect, useState } from 'react'; +import { + LoadingOverlay, + Modal, + ModalBody, + ModalHeader, +} from '@deephaven/components'; +import type DhType from '@deephaven/jsapi-types'; +import Log from '@deephaven/log'; + +const log = Log.module('@deephaven/code-studio'); + +export const ApiContext = createContext(null); + +export type ApiBootstrapProps = { + apiUrl: string; + children: JSX.Element; + failureElement?: JSX.Element; + setGlobally?: boolean; +}; + +export function ApiBootstrap({ + apiUrl, + children, + failureElement, + setGlobally = false, +}: ApiBootstrapProps) { + const [isLoading, setIsLoading] = useState(true); + const [api, setApi] = useState(); + useEffect(() => { + async function loadApi() { + try { + const dh: DhType = (await import(/* @vite-ignore */ apiUrl)).default; + log.info('API bootstrapped from', apiUrl); + setApi(dh); + if (setGlobally) { + log.debug('API set globally'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).dh = dh; + } + } catch (e) { + log.error('Unable to bootstrap API', e); + } + setIsLoading(false); + } + loadApi(); + }, [apiUrl, setGlobally]); + + if (isLoading) { + return ; + } + if (api == null) { + return ( + failureElement ?? ( + + + Error: Unable to load API + + +

+ Ensure the server is running and you are able to reach {apiUrl}, + then refresh the page. +

+
+
+ ) + ); + } + return {children}; +} + +export default ApiBootstrap; diff --git a/packages/jsapi-bootstrap/src/index.ts b/packages/jsapi-bootstrap/src/index.ts new file mode 100644 index 0000000000..3bbd44d868 --- /dev/null +++ b/packages/jsapi-bootstrap/src/index.ts @@ -0,0 +1,2 @@ +export * from './ApiBootstrap'; +export * from './useApi'; diff --git a/packages/jsapi-bootstrap/src/useApi.ts b/packages/jsapi-bootstrap/src/useApi.ts new file mode 100644 index 0000000000..54b6171d7f --- /dev/null +++ b/packages/jsapi-bootstrap/src/useApi.ts @@ -0,0 +1,14 @@ +import { useContext } from 'react'; +import { ApiContext } from './ApiBootstrap'; + +export const useApi = () => { + const dh = useContext(ApiContext); + if (dh == null) { + throw new Error( + 'No API available in useApi. Was code wrapped in ApiBootstrap or ApiContext.Provider?' + ); + } + return dh; +}; + +export default useApi; diff --git a/packages/jsapi-bootstrap/tsconfig.json b/packages/jsapi-bootstrap/tsconfig.json new file mode 100644 index 0000000000..20bd393c00 --- /dev/null +++ b/packages/jsapi-bootstrap/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src/", + "outDir": "dist/" + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], + "exclude": ["node_modules", "src/**/*.test.*", "src/**/__mocks__/*"], + "references": [ + { + "path": "../components" + }, + { + "path": "../jsapi-shim" + }, + { + "path": "../jsapi-utils" + }, + { + "path": "../log" + } + ] +} diff --git a/packages/jsapi-shim/package.json b/packages/jsapi-shim/package.json index fe7cc1ab85..567e3d0df1 100644 --- a/packages/jsapi-shim/package.json +++ b/packages/jsapi-shim/package.json @@ -21,6 +21,7 @@ "build:babel": "babel ./src --out-dir ./dist --extensions \".ts,.tsx,.js,.jsx\" --source-maps --root-mode upward" }, "dependencies": { + "@deephaven/jsapi-types": "file:../jsapi-types", "prop-types": "^15.7.2" }, "devDependencies": { diff --git a/packages/jsapi-shim/src/dh.ts b/packages/jsapi-shim/src/dh.ts index 7b53aacbf1..ed2ac9c997 100644 --- a/packages/jsapi-shim/src/dh.ts +++ b/packages/jsapi-shim/src/dh.ts @@ -1,6 +1,6 @@ // The Deephaven API script isn't packaged as a module (yet), and is just included in index.html, exported to the global namespace // This include file is simply a wrapper so that it behaves like a module, and can be mocked easily for unit tests -import type dhType from './dh.types'; +import type dhType from '@deephaven/jsapi-types'; declare global { // eslint-disable-next-line vars-on-top,no-var diff --git a/packages/jsapi-shim/src/dh.types.ts b/packages/jsapi-shim/src/dh.types.ts index cdec306ea6..0f302b8e8b 100644 --- a/packages/jsapi-shim/src/dh.types.ts +++ b/packages/jsapi-shim/src/dh.types.ts @@ -1,1055 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/ban-types */ -/* eslint-disable @typescript-eslint/no-empty-interface */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable lines-between-class-members */ +import dh from '@deephaven/jsapi-types'; -/* eslint-disable max-classes-per-file */ -export default dh; - -export interface dh { - IdeConnection: IdeConnectionConstructor; - Session: IdeSession; - VariableType: typeof VariableType; - i18n: { - DateTimeFormat: DateTimeFormat; - NumberFormat: NumberFormat; - TimeZone: i18nTimeZone; - }; - DateWrapper: DateWrapper; - LongWrapper: LongWrapper; - FilterCondition: FilterConditionStatic; - FilterValue: FilterValueStatic; - plot: Plot; - Axis: Axis; - Table: TableStatic; - Client: ClientStatic; - TreeTable: TreeTableStatic; - Column: Column; - SearchDisplayMode?: SearchDisplayModeStatic; - RangeSet: RangeSet; - IdeSession: IdeSessionStatic; - calendar: CalendarStatic; - CoreClient: CoreClientContructor; - storage: { - FileContents: FileContentsStatic; - }; - ValueType: ValueTypeStatic; -} - -const VariableType = { - FIGURE: 'Figure', - OTHERWIDGET: 'OtherWidget', - PANDAS: 'Pandas', - TABLE: 'Table', - TABLEMAP: 'TableMap', - TREETABLE: 'TreeTable', - HIERARCHICALTABLE: 'HierarchicalTable', - PARTITIONEDTABLE: 'PartitionedTable', -} as const; - -export interface ValueTypeStatic { - STRING: 'String'; - NUMBER: 'Number'; - DOUBLE: 'Double'; - LONG: 'Long'; - DATETIME: 'Datetime'; - BOOLEAN: 'Boolean'; -} - -export type ValueTypeUnion = typeof dh.ValueType[keyof typeof dh.ValueType]; - -export interface CalendarStatic { - DayOfWeek: { values: () => string[] }; -} - -export type VariableTypeUnion = typeof VariableType[keyof typeof VariableType]; - -export interface VariableDefinition< - T extends VariableTypeUnion = VariableTypeUnion -> { - type: T; - - /** - * @deprecated - */ - name?: string; - - title?: string; - - id?: string; -} - -export interface LogItem { - micros: number; - logLevel: string; - message: string; -} - -export interface VariableChanges { - created: VariableDefinition[]; - updated: VariableDefinition[]; - removed: VariableDefinition[]; -} - -export interface CommandResult { - changes: VariableChanges; - error: string; -} - -export interface Position { - line: number; - character: number; -} - -export interface DocumentRange { - start: Position; - end: Position; -} - -export interface TextEdit { - text: string; - range: DocumentRange; -} - -export interface CompletionItem { - label: string; - kind: number; - detail: string; - documentation: string; - sortText: string; - filterText: string; - textEdit: TextEdit; - insertTextFormat: number; -} - -export interface IdeSessionStatic { - EVENT_COMMANDSTARTED: 'commandstarted'; -} - -export interface WorkerConnection { - subscribeToFieldUpdates( - param: (changes: VariableChanges) => void - ): () => void; -} - -export interface IdeSession extends Evented { - subscribeToFieldUpdates( - param: (changes: VariableChanges) => void - ): () => void; - getTable(name: string): Promise; - getFigure(name: string): Promise
; - getTreeTable(name: string): Promise; - getObject( - definition: VariableDefinition - ): Promise
; - getObject( - definition: VariableDefinition - ): Promise
; - getObject( - definition: VariableDefinition - ): Promise; - getObject( - definition: VariableDefinition - ): Promise; - getObject(definition: VariableDefinition): Promise; - onLogMessage(logHandler: (logItem: LogItem) => void): () => void; - runCode(code: string): Promise; - bindTableToVariable(table: Table, variableName: string): Promise; - mergeTables(tables: Table[]): Promise
; - newTable( - columnNames: string[], - columnTypes: string[], - data: string[][], - userTimeZone: string - ): Promise
; - getCompletionItems(params: unknown): Promise; - closeDocument(params: unknown): void; - openDocument(params: unknown): void; - changeDocument(params: unknown): void; - close(): void; -} - -export interface Evented { - addEventListener(eventType: string, listener: EventListener): RemoverFn; - nextEvent(eventType: string, timeoutInMillis?: number): Promise; - - hasListeners(eventType: string): boolean; - removeEventListener(eventType: string, listener: EventListener): boolean; -} - -export interface Plot { - Figure: Figure; - - SourceType: SourceType; - SeriesPlotStyle: SeriesPlotStyle; - ChartType: ChartType; - AxisType: AxisType; - AxisPosition: AxisPosition; - AxisFormatType: AxisFormatType; - - FigureDescriptor: FigureDescriptor; - ChartDescriptor: ChartDescriptor; - SeriesDescriptor: SeriesDescriptor; - SourceDescriptor: SourceDescriptor; - DownsampleOptions: DownsampleOptions; -} - -export interface RemoverFn { - (): void; -} - -export interface EventListener { - (event: CustomEvent): void; -} - -export interface FigureDescriptor { - title: string; - titleFont?: string; - titleColor?: string; - isResizable?: boolean; - isDefaultTheme?: boolean; - updateInterval?: number; - rows?: number; - cols?: number; - charts: Partial[]; -} -export interface ChartDescriptor { - rowspan?: number; - colspan?: number; - series: Partial[]; - axes: Partial[]; - chartType: string; - title?: string; - titleFont?: string; - titleColor?: string; - showLegend?: boolean; - legendFont?: string; - legendColor?: string; - is3d?: boolean; -} -export interface SeriesDescriptor { - plotStyle: string; - name: string; - linesVisible?: boolean; - shapesVisible?: boolean; - gradientVisible?: boolean; - lineColor?: string; - pointLabelFormat?: string; - xToolTipPattern?: string; - yToolTipPattern?: string; - shapeLabel?: string; - shapeSize?: number; - shapeColor?: string; - shape?: string; -} -export interface SourceDescriptor { - axis?: AxisDescriptor; - table: Table; - columnName: string; - columnType: string; -} -export interface AxisDescriptor { - formatType: string; - type: string; - position: string; - log?: boolean; - label?: string; - labelFont?: string; - ticksFont?: string; - formatPattern?: string; - color?: string; - minRange?: number; - maxRange?: number; - minorTicksVisible?: boolean; - majorTicksVisible?: boolean; - minorTickCount?: number; - gapBetweenMajorTicks?: number; - tickLabelAngle?: number; - invert?: boolean; - isTimeAxis?: boolean; -} -export interface Figure extends Evented { - readonly EVENT_UPDATED: string; - readonly EVENT_DISCONNECT: string; - readonly EVENT_RECONNECT: string; - readonly EVENT_RECONNECTFAILED: string; - readonly EVENT_DOWNSAMPLESTARTED: string; - readonly EVENT_DOWNSAMPLEFINISHED: string; - readonly EVENT_DOWNSAMPLEFAILED: string; - readonly EVENT_DOWNSAMPLENEEDED: string; - readonly EVENT_SERIES_ADDED: string; - - /** Given a client-created figure descriptor, generate a figure that can be subscribed to */ - create(figure: Partial): Figure; - - readonly title: string; - readonly titleFont: string; - readonly titleColor: string; - readonly isResizable: boolean; - readonly isDefaultTheme: boolean; - readonly updateInterval: number; - readonly cols: number; - readonly rows: number; - readonly charts: Chart[]; - - /** - * Subscribes to all series in this figure. - * @param forceDisableDownsample optional, can be specified to force downsampling to be disabled - */ - subscribe(forceDisableDownsample?: DownsampleOptions): void; - - /** - * Unsubscribes to all series in this figure. - */ - unsubscribe(): void; - - close(): void; -} - -export interface FigureDataUpdatedEvent { - /** - * The series instances which were affected by this event and need to be updated. - */ - readonly series: Series[]; - - /** - * Reads data out for this series from the event which just occurred for the given series. - * The array returned by this method will be cached and reused to minimize the garbage - * created, and to reduce processing time in handling updates. - * - * The pattern when using this is to iterate over the series which this event affects, and - * for each series, iterate over each source. For each series+source, pass them in, along - * with the optional mapping function, and get back the array of data across the entire plot. - * - * @param series - * @param sourceType - * @param mapFn - */ - getArray( - series: Series, - sourceType: SourceType, - mapFn?: MapFn - ): O[]; -} - -export interface MapFn { - (input: I): O; -} - -export interface DownsampleOptions { - readonly DEFAULT: DownsampleOptions; - readonly DISABLE: DownsampleOptions; -} - -export interface SourceType { - readonly X: SourceType; - readonly Y: SourceType; - readonly Z: SourceType; - readonly X_LOW: SourceType; - readonly X_HIGH: SourceType; - readonly Y_LOW: SourceType; - readonly Y_HIGH: SourceType; - readonly TIME: SourceType; - readonly OPEN: SourceType; - readonly HIGH: SourceType; - readonly LOW: SourceType; - readonly CLOSE: SourceType; - readonly SHAPE: SourceType; - readonly SIZE: SourceType; - readonly LABEL: SourceType; - readonly COLOR: SourceType; - readonly PARENT: SourceType; - readonly HOVER_TEXT: SourceType; - readonly TEXT: SourceType; -} -export interface ChartType { - readonly XY: ChartType; - readonly PIE: ChartType; - readonly OHLC: ChartType; - readonly CATEGORY: ChartType; - readonly XYZ: ChartType; - readonly CATEGORY_3D: ChartType; -} -export interface SeriesPlotStyle { - readonly BAR: SeriesPlotStyle; - readonly STACKED_BAR: SeriesPlotStyle; - readonly LINE: SeriesPlotStyle; - readonly AREA: SeriesPlotStyle; - readonly STACKED_AREA: SeriesPlotStyle; - readonly PIE: SeriesPlotStyle; - readonly HISTOGRAM: SeriesPlotStyle; - readonly OHLC: SeriesPlotStyle; - readonly SCATTER: SeriesPlotStyle; - readonly STEP: SeriesPlotStyle; - readonly ERROR_BAR: SeriesPlotStyle; - readonly TREEMAP: SeriesPlotStyle; -} -export interface AxisFormatType { - readonly CATEGORY: AxisFormatType; - readonly NUMBER: AxisFormatType; -} -export interface AxisType { - readonly X: AxisType; - readonly Y: AxisType; - readonly Z: AxisType; - readonly SHAPE: AxisType; - readonly SIZE: AxisType; - readonly LABEL: AxisType; - readonly COLOR: AxisType; -} -export interface AxisPosition { - readonly TOP: AxisPosition; - readonly BOTTOM: AxisPosition; - readonly LEFT: AxisPosition; - readonly RIGHT: AxisPosition; - readonly NONE: AxisPosition; -} - -export interface Chart extends Evented { - readonly EVENT_SERIES_ADDED: string; - - readonly row: number; - readonly column: number; - readonly colspan: number; - readonly rowspan: number; - readonly chartType: ChartType; - readonly title: string; - readonly titleFont: string; - readonly titleColor: string; - readonly showLegend: boolean; - readonly legendFont: string; - readonly legendColor: string; - readonly is3d: boolean; - readonly series: Series[]; - readonly multiSeries: MultiSeries[]; - readonly axes: Axis[]; -} - -export interface Series { - readonly plotStyle: SeriesPlotStyle; - readonly name: string; - readonly isLinesVisible: boolean | null; - readonly isShapesVisible: boolean | null; - readonly isGradientVisible: boolean; - readonly lineColor: string; - readonly pointLabelFormat: string; - readonly xToolTipPattern: string; - readonly yToolTipPattern: string; - readonly shapeLabel: string; - readonly shapeSize: number; - readonly shape: string; - readonly shapeColor: string; - readonly sources: SeriesDataSource[]; - readonly multiSeries: MultiSeries; - readonly oneClick: OneClick; - - subscribe(downsampleOptions?: DownsampleOptions): void; - unsubscribe(): void; -} - -export interface MultiSeries { - readonly plotStyle: SeriesPlotStyle; - readonly name: string; -} - -export interface BusinessPeriod { - open: string; - close: string; -} - -export interface LocalDateWrapper { - toString: () => string; -} -export interface Holiday { - date: LocalDateWrapper; - businessPeriods: BusinessPeriod[]; -} - -export interface BusinessCalendar { - getName: () => string; - timeZone: TimeZone; - businessPeriods: BusinessPeriod[]; - businessDays: string[]; - holidays: Holiday[]; -} - -export interface Axis { - readonly id: string; - readonly formatType: AxisFormatType; - readonly type: AxisType; - readonly position: AxisPosition; - readonly log: boolean; - readonly label: string; - readonly labelFont: string; - readonly ticksFont: string; - readonly formatPattern: string; - readonly minRange: number; - readonly maxRange: number; - readonly isMinorTicksVisible: boolean; - readonly isMajorTicksVisible: boolean; - readonly minorTickCount: number; - readonly gapBetweenMajorTicks: number; - readonly majorTickLocations: number[]; - readonly tickLabelAngle: number; - readonly isInvert: boolean; - readonly isTimeAxis: boolean; - - readonly FORMAT_TYPE_NUMBER: unknown; - readonly businessCalendar: BusinessCalendar; - - /** - * Indicate the density and range of data that the UI needs for this axis, across any series which - * draws on this axis. Ignored for non-time series data, for non-line series. - * @param pixelCount the approx number of pixels wide - * @param min the optional minimum value visible on this axis - even if specified, smaller values may - * be returned, to ensure that lines drawn off the screen. - * @param max the optional max value visible on this axis. If min is specified, max is also expected. - */ - range(pixelCount?: number, min?: any, max?: any): void; -} - -export interface SeriesDataSource { - readonly axis: Axis; - readonly type: SourceType; - readonly columnType: string; -} - -export interface OneClick { - readonly columns: { name: string; type: string }[]; - readonly requireAllFiltersToDisplay: boolean; - - setValueForColumn(columnName: string, value: any): void; - getValueForColumn(columnName: string): any; -} - -export interface Column { - /** - * @deprecated - */ - readonly index: number; - - readonly type: string; - readonly name: string; - readonly description: string; - readonly constituentType: string; - - readonly isPartitionColumn: boolean; - - filter(): FilterValue; - sort(): Sort; - - formatColor(expression: string): CustomColumn; - formatRowColor(expression: string): CustomColumn; -} - -export interface CustomColumn { - readonly type: string; - readonly name: string; - readonly expression: string; -} - -export interface FilterValueStatic { - ofString(input: unknown): FilterValue; - ofNumber(input: unknown): FilterValue; - ofBoolean(input: unknown): FilterValue; -} -export interface FilterValue { - eq(value: FilterValue): FilterCondition; - eqIgnoreCase(value: FilterValue): FilterCondition; - notEq(value: FilterValue): FilterCondition; - notEqIgnoreCase(value: FilterValue): FilterCondition; - greaterThan(value: FilterValue): FilterCondition; - lessThan(value: FilterValue): FilterCondition; - greaterThanOrEqualTo(value: FilterValue): FilterCondition; - lessThanOrEqualTo(value: FilterValue): FilterCondition; - in(values: FilterValue[]): FilterCondition; - inIgnoreCase(values: FilterValue[]): FilterCondition; - notIn(values: FilterValue[]): FilterCondition; - notInIgnoreCase(values: FilterValue[]): FilterCondition; - contains(value: FilterValue): FilterCondition; - isFalse(): FilterCondition; - isTrue(): FilterCondition; - isNull(): FilterCondition; - invoke(method: string, ...args: FilterValue[]): FilterCondition; -} - -export interface FilterConditionStatic { - invoke(method: string, ...args: FilterValue[]): FilterCondition; - search(value: FilterValue, columns?: FilterValue[]): FilterCondition; -} -export interface FilterCondition { - not(): FilterCondition; - and(first: FilterCondition, ...rest: FilterCondition[]): FilterCondition; - or(first: FilterCondition, ...rest: FilterCondition[]): FilterCondition; - - toString(): string; -} -export interface Sort { - reverse(): Sort; - - readonly column: Column; - readonly direction: 'ASC' | 'DESC' | 'REVERSE' | null; - - readonly isAbs: boolean; - - asc(): Sort; - desc(): Sort; - abs(): Sort; -} - -export interface InputTable { - keys: string[]; - keyColumns: Column[]; - values: string[]; - valueColumns: Column[]; - addRow( - row: Record, - userTimeZone?: string - ): Promise; - addRows( - rows: Record[], - userTimeZone?: string - ): Promise; - addTable(table: Table): Promise; - addTables(tables: Table[]): Promise; - deleteTable(table: Table): Promise; - deleteTables(tables: Table[]): Promise; - table: Table; -} -export interface ColumnGroup { - name: string; - children: string[]; - color?: string; -} - -export interface LayoutHints { - areSavedLayoutsAllowed?: boolean; - frontColumns?: string[]; - backColumns?: string[]; - hiddenColumns?: string[]; - frozenColumns?: string[]; - columnGroups?: ColumnGroup[]; - searchDisplayMode?: keyof SearchDisplayModeStatic; -} - -export interface SearchDisplayModeStatic { - SEARCH_DISPLAY_DEFAULT: 'Default'; - SEARCH_DISPLAY_HIDE: 'Hide'; - SEARCH_DISPLAY_SHOW: 'Show'; -} - -export interface TableStatic { - readonly EVENT_SIZECHANGED: string; - readonly EVENT_UPDATED: string; - readonly EVENT_ROWADDED: string; - readonly EVENT_ROWREMOVED: string; - readonly EVENT_ROWUPDATED: string; - readonly EVENT_SORTCHANGED: string; - readonly EVENT_FILTERCHANGED: string; - readonly EVENT_CUSTOMCOLUMNSCHANGED: string; - readonly EVENT_DISCONNECT: string; - readonly EVENT_RECONNECT: string; - readonly EVENT_RECONNECTFAILED: string; - readonly SIZE_UNCOALESCED: number; - reverse(): Sort; -} - -export interface ClientStatic { - readonly EVENT_REQUEST_FAILED: 'requestfailed'; - readonly EVENT_REQUEST_STARTED: 'requeststarted'; - readonly EVENT_REQUEST_SUCCEEDED: 'requestsucceeded'; -} -export interface Table extends TableTemplate
, TableStatic { - readonly totalSize: number; - - readonly description: string; - - customColumns: string[]; +export * from '@deephaven/jsapi-types'; - readonly layoutHints: LayoutHints; - - readonly isUncoalesced: boolean; - readonly hasInputTable: boolean; - - readonly isClosed: boolean; - readonly pluginName: string; - - applyCustomColumns(columns: (CustomColumn | string)[]): string[]; - - getViewportData(): Promise; - - subscribe(columns: Column[]): TableSubscription; - - selectDistinct(columns: Column[]): Promise
; - copy(): Promise
; - - rollup(config: RollupConfig): Promise; - treeTable(config: TreeTableConfig): Promise; - - inputTable(): Promise; - - freeze(): Promise
; - - snapshot( - rightHandSide: Table, - doInitialSnapshot?: boolean, - stampColumns?: string[] - ): Promise
; - - getColumnStatistics(column: Column): Promise; - - join( - joinType: string, - rightTable: Table, - columnsToMatch: string[], - columnsToAdd?: string[] - ): Promise
; - byExternal(keys: string[], dropKeys?: boolean): Promise; - - fireViewportUpdate(): void; - - seekRow( - startRow: number, - column: Column, - valueType: ValueTypeUnion, - value: unknown, - insensitive?: boolean, - contains?: boolean, - isBackwards?: boolean - ): Promise; -} - -export interface TableViewportSubscription extends Evented { - setViewport(firstRow: number, lastRow: number, columns?: Column[]): void; - getViewportData(): Promise; - snapshot(rows: RangeSet, columns: readonly Column[]): Promise; - close(): void; -} - -export interface ViewportData { - offset: number; - rows: Row[]; - columns: Column[]; -} - -export interface TableSubscription extends Evented { - readonly EVENT_UPDATED: string; - - readonly columns: Column[]; - close(): void; -} - -export interface RangeSet { - ofRange(first: number, last: number): RangeSet; - ofItems(rows: number[]): RangeSet; - ofRanges(ranges: RangeSet[]): RangeSet; - - readonly size: number; - iterator(): JsIterator; -} - -export interface JsIterator { - hasNext(): boolean; - next(): IteratorResult; -} - -export interface LongWrapper { - asNumber(): number; - valueOf(): string; - toString(): string; - ofString(str: string): LongWrapper; -} -export interface DateWrapper extends LongWrapper { - ofJsDate(date: Date): DateWrapper; - asDate(): Date; -} - -export interface TimeZone { - adjustments: number[]; - standardOffset: number; - timeZoneID: string; - id: string; - transitionPoints: number[]; - tzNames: string[]; -} - -export interface i18nTimeZone { - getTimeZone(tzCode: string): TimeZone; -} - -export interface DateTimeFormat { - format( - pattern: string, - date: DateWrapper | Date | number, - timeZone?: TimeZone - ): string; - parse(pattern: string, text: string, timeZone?: TimeZone): DateWrapper; - parseAsDate(pattern: string, text: string): Date; -} - -export interface NumberFormat { - format(pattern: string, number: number): string; - parse(pattern: string, text: string): number; -} - -export interface TableData { - readonly columns: Column[]; - readonly rows: Row[]; - - get(index: number): Row; - get(index: LongWrapper): Row; - - getData(index: number, column: Column): any; - getData(index: LongWrapper, column: Column): any; - - getFormat(index: number, column: Column): Format; - getFormat(index: LongWrapper, column: Column): Format; -} - -export interface UpdateEventData extends TableData { - readonly added: RangeSet; - readonly removed: RangeSet; - readonly modified: RangeSet; - readonly fullIndex: RangeSet; -} - -export interface Row { - readonly index: LongWrapper; - - get(column: Column): any; - - getFormat(column: Column): Format; -} - -export interface Format { - readonly color: string; - readonly backgroundColor: string; - readonly formatString: string; -} - -export interface ColumnStatistics { - readonly statisticsMap: Map; - readonly uniqueValues: Map; - - getType(name: string): string; -} - -export interface TreeTableStatic { - readonly EVENT_UPDATED: string; - readonly EVENT_DISCONNECT: string; - readonly EVENT_RECONNECT: string; - readonly EVENT_RECONNECTFAILED: string; -} - -export interface TableTemplate extends Evented { - readonly size: number; - readonly columns: Column[]; - readonly sort: Sort[]; - readonly filter: FilterCondition[]; - readonly totalsTableConfig: TotalsTableConfig; - - findColumn(name: string): Column; - findColumns(names: string[]): Column[]; - - applySort(sorts: Sort[]): Sort[]; - applyFilter(filters: FilterCondition[]): FilterCondition[]; - selectDistinct(columns: Column[]): Promise
; - - getTotalsTable(config?: TotalsTableConfig): Promise; - getGrandTotalsTable(config?: TotalsTableConfig): Promise; - - setViewport( - firstRow: number, - lastRow: number, - columns?: Column[], - updateIntervalMs?: number - ): TableViewportSubscription; - - copy(): Promise; - close(): void; -} - -export interface TreeTable extends TableTemplate, TreeTableStatic { - readonly isIncludeConstituents: boolean; - readonly groupedColumns: Column[]; - - expand(row: number): void; - expand(row: TreeRow): void; - collapse(row: number): void; - collapse(row: TreeRow): void; - setExpanded( - row: number, - isExpanded: boolean, - expandDescendants?: boolean - ): void; - setExpanded( - row: TreeRow, - isExpanded: boolean, - expandDescendants?: boolean - ): void; - expandAll?(): void; - collapseAll?(): void; - isExpanded(row: number): boolean; - isExpanded(row: TreeRow): boolean; - - getViewportData(): Promise; - - saveExpandedState(): string; - restoreExpandedState(nodesToRestore: string): void; -} -export interface TreeTableData extends TableData { - readonly rows: TreeRow[]; -} -export interface TreeRow extends Row { - readonly isExpanded: boolean; - readonly hasChildren: boolean; - readonly depth: number; -} - -export interface RollupConfig { - groupingColumns: string[] | null; - aggregations: Record | null; - includeConstituents: boolean; - includeOriginalColumns?: boolean; - includeDescriptions: boolean; -} - -export interface TreeTableConfig {} - -export interface TotalsTableConfig { - showTotalsByDefault?: boolean; - showGrandTotalsByDefault?: boolean; - defaultOperation?: string; - groupBy?: readonly string[]; - operationMap: Record; -} - -export interface TotalsTable extends Evented { - readonly size: number; - readonly columns: Column[]; - - readonly sort: Sort[]; - readonly filter: FilterCondition[]; - customColumns: string[]; - - readonly totalsTableConfig: TotalsTableConfig; - - applySort(sorts: Sort[]): Sort[]; - applyFilter(filters: FilterCondition[]): FilterCondition[]; - applyCustomColumns(columns: string[]): string[]; - - setViewport( - firstRow: number, - lastRow: number, - columns?: Column[], - updateIntervalMs?: number - ): void; - getViewportData(): Promise; - - close(): void; -} - -export interface TableMap extends Evented { - readonly size: number; - close(): void; - getKeys(): Promise>; - getTable(key: object): Promise
; -} - -export interface WorkerHeapInfo { - readonly maximumHeapSize: number; - readonly freeMemory: number; - readonly totalHeapSize: number; -} - -export interface QueryConnectable extends Evented { - getWorkerHeapInfo(): Promise; - getConsoleTypes(): Promise; - startSession(type: string): Promise; -} - -export interface IdeConnectionOptions { - authoToken?: string; - serviceId?: string; -} - -export interface IdeConnectionConstructor { - HACK_CONNECTION_FAILURE: string; - new (serverUrl: string, options?: IdeConnectionOptions): IdeConnection; -} - -export interface IdeConnection - extends QueryConnectable, - IdeConnectionConstructor { - close(): void; - running(): Promise; - disconnected(): void; - getObject( - definition: VariableDefinition - ): Promise
; - getObject( - definition: VariableDefinition - ): Promise
; - getObject( - definition: VariableDefinition - ): Promise; - getObject( - definition: VariableDefinition - ): Promise; - getObject(definition: VariableDefinition): Promise; - subscribeToFieldUpdates( - param: (changes: VariableChanges) => void - ): () => void; -} - -export interface ItemDetails { - filename: string; - basename: string; - dirname: string; - type: 'directory' | 'file'; - size: number; - etag?: string; -} - -export interface FileContentsStatic { - blob(blob: Blob): FileContents; - text(...text: string[]): FileContents; - arrayBuffers(...buffers: ArrayBuffer[]): FileContents; -} - -export interface FileContents { - text(): Promise; - arrayBuffer(): Promise; - etag?: string; -} - -export interface LoginOptions { - type: string; - token?: string; -} - -export interface StorageService { - listItems(path: string, glob?: string): Promise; - loadFile(path: string, etag?: string): Promise; - deleteItem(path: string): Promise; - saveFile( - path: string, - contents: FileContents, - allowOverwrite?: boolean - ): Promise; - moveItem(path: string, newPath: string, newFile?: boolean): Promise; - createDirectory(path: string): Promise; -} - -export interface CoreClientContructor { - LOGIN_TYPE_ANONYMOUS: string; - new (serverUrl: string): CoreClient; -} - -export interface CoreClient extends CoreClientContructor { - login(options: LoginOptions): Promise; - getAsIdeConnection(): Promise; - getStorageService(): StorageService; - getServerConfigValues(): [string, string][]; -} +export default dh; diff --git a/packages/jsapi-shim/tsconfig.json b/packages/jsapi-shim/tsconfig.json index 8c183dff36..802780c573 100644 --- a/packages/jsapi-shim/tsconfig.json +++ b/packages/jsapi-shim/tsconfig.json @@ -5,5 +5,10 @@ "outDir": "dist/" }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], - "exclude": ["node_modules", "src/**/*.test.*", "src/**/__mocks__/*"] + "exclude": ["node_modules", "src/**/*.test.*", "src/**/__mocks__/*"], + "references": [ + { + "path": "../jsapi-types" + } + ] } diff --git a/packages/jsapi-types/.gitignore b/packages/jsapi-types/.gitignore new file mode 100644 index 0000000000..742a2cf9d2 --- /dev/null +++ b/packages/jsapi-types/.gitignore @@ -0,0 +1,31 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build +/dist + +# misc +.vscode +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +.project +.settings/ +.eslintcache +.stylelintcache + +/public/vs + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +src/**/*.css diff --git a/packages/jsapi-types/README.md b/packages/jsapi-types/README.md new file mode 100644 index 0000000000..feae130cdf --- /dev/null +++ b/packages/jsapi-types/README.md @@ -0,0 +1,17 @@ +# @deephaven/jsapi-types + +Manually created type definitions of the Deephaven JS API. + +## Install + +```bash +npm install --save @deephaven/jsapi-types +``` + +## Usage + +```javascript +import { Column } from '@deephaven/jsapi-types' + +... +``` diff --git a/packages/jsapi-types/jest.config.cjs b/packages/jsapi-types/jest.config.cjs new file mode 100644 index 0000000000..365815a412 --- /dev/null +++ b/packages/jsapi-types/jest.config.cjs @@ -0,0 +1,7 @@ +const baseConfig = require('../../jest.config.base.cjs'); +const packageJson = require('./package'); + +module.exports = { + ...baseConfig, + displayName: packageJson.name, +}; diff --git a/packages/jsapi-types/package.json b/packages/jsapi-types/package.json new file mode 100644 index 0000000000..110094d5e0 --- /dev/null +++ b/packages/jsapi-types/package.json @@ -0,0 +1,32 @@ +{ + "name": "@deephaven/jsapi-types", + "version": "0.29.0", + "description": "Deephaven JSAPI Types", + "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/jsapi-types" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "source": "src/index.ts", + "engines": { + "node": ">=16" + }, + "scripts": { + "build": "cross-env NODE_ENV=production run-p build:*", + "build:babel": "babel ./src --out-dir ./dist --extensions \".ts,.tsx,.js,.jsx\" --source-maps --root-mode upward" + }, + "devDependencies": { + "@deephaven/tsconfig": "file:../tsconfig" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/jsapi-types/src/dh.types.ts b/packages/jsapi-types/src/dh.types.ts new file mode 100644 index 0000000000..d6f9381f48 --- /dev/null +++ b/packages/jsapi-types/src/dh.types.ts @@ -0,0 +1,1055 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable lines-between-class-members */ + +/* eslint-disable max-classes-per-file */ +export default dh; + +export interface dh { + IdeConnection: IdeConnectionConstructor; + Session: IdeSession; + VariableType: typeof VariableType; + i18n: { + DateTimeFormat: DateTimeFormat; + NumberFormat: NumberFormat; + TimeZone: i18nTimeZone; + }; + DateWrapper: DateWrapper; + LongWrapper: LongWrapper; + FilterCondition: FilterConditionStatic; + FilterValue: FilterValueStatic; + plot: Plot; + Axis: Axis; + Table: TableStatic; + Client: ClientStatic; + TreeTable: TreeTableStatic; + Column: Column; + SearchDisplayMode?: SearchDisplayModeStatic; + RangeSet: RangeSet; + IdeSession: IdeSessionStatic; + calendar: CalendarStatic; + CoreClient: CoreClientContructor; + storage: { + FileContents: FileContentsStatic; + }; + ValueType: typeof ValueType; +} + +const VariableType = { + FIGURE: 'Figure', + OTHERWIDGET: 'OtherWidget', + PANDAS: 'Pandas', + TABLE: 'Table', + TABLEMAP: 'TableMap', + TREETABLE: 'TreeTable', + HIERARCHICALTABLE: 'HierarchicalTable', + PARTITIONEDTABLE: 'PartitionedTable', +} as const; + +const ValueType = { + STRING: 'String', + NUMBER: 'Number', + DOUBLE: 'Double', + LONG: 'Long', + DATETIME: 'Datetime', + BOOLEAN: 'Boolean', +} as const; + +export type ValueTypeUnion = typeof ValueType[keyof typeof ValueType]; + +export interface CalendarStatic { + DayOfWeek: { values: () => string[] }; +} + +export type VariableTypeUnion = typeof VariableType[keyof typeof VariableType]; + +export interface VariableDefinition< + T extends VariableTypeUnion = VariableTypeUnion +> { + type: T; + + /** + * @deprecated + */ + name?: string; + + title?: string; + + id?: string; +} + +export interface LogItem { + micros: number; + logLevel: string; + message: string; +} + +export interface VariableChanges { + created: VariableDefinition[]; + updated: VariableDefinition[]; + removed: VariableDefinition[]; +} + +export interface CommandResult { + changes: VariableChanges; + error: string; +} + +export interface Position { + line: number; + character: number; +} + +export interface DocumentRange { + start: Position; + end: Position; +} + +export interface TextEdit { + text: string; + range: DocumentRange; +} + +export interface CompletionItem { + label: string; + kind: number; + detail: string; + documentation: string; + sortText: string; + filterText: string; + textEdit: TextEdit; + insertTextFormat: number; +} + +export interface IdeSessionStatic { + EVENT_COMMANDSTARTED: 'commandstarted'; +} + +export interface WorkerConnection { + subscribeToFieldUpdates( + param: (changes: VariableChanges) => void + ): () => void; +} + +export interface IdeSession extends Evented { + subscribeToFieldUpdates( + param: (changes: VariableChanges) => void + ): () => void; + getTable(name: string): Promise
; + getFigure(name: string): Promise
; + getTreeTable(name: string): Promise; + getObject( + definition: VariableDefinition + ): Promise
; + getObject( + definition: VariableDefinition + ): Promise
; + getObject( + definition: VariableDefinition + ): Promise; + getObject( + definition: VariableDefinition + ): Promise; + getObject(definition: VariableDefinition): Promise; + onLogMessage(logHandler: (logItem: LogItem) => void): () => void; + runCode(code: string): Promise; + bindTableToVariable(table: Table, variableName: string): Promise; + mergeTables(tables: Table[]): Promise
; + newTable( + columnNames: string[], + columnTypes: string[], + data: string[][], + userTimeZone: string + ): Promise
; + getCompletionItems(params: unknown): Promise; + closeDocument(params: unknown): void; + openDocument(params: unknown): void; + changeDocument(params: unknown): void; + close(): void; +} + +export interface Evented { + addEventListener(eventType: string, listener: EventListener): RemoverFn; + nextEvent(eventType: string, timeoutInMillis?: number): Promise; + + hasListeners(eventType: string): boolean; + removeEventListener(eventType: string, listener: EventListener): boolean; +} + +export interface Plot { + Figure: Figure; + + SourceType: SourceType; + SeriesPlotStyle: SeriesPlotStyle; + ChartType: ChartType; + AxisType: AxisType; + AxisPosition: AxisPosition; + AxisFormatType: AxisFormatType; + + FigureDescriptor: FigureDescriptor; + ChartDescriptor: ChartDescriptor; + SeriesDescriptor: SeriesDescriptor; + SourceDescriptor: SourceDescriptor; + DownsampleOptions: DownsampleOptions; +} + +export interface RemoverFn { + (): void; +} + +export interface EventListener { + (event: CustomEvent): void; +} + +export interface FigureDescriptor { + title: string; + titleFont?: string; + titleColor?: string; + isResizable?: boolean; + isDefaultTheme?: boolean; + updateInterval?: number; + rows?: number; + cols?: number; + charts: Partial[]; +} +export interface ChartDescriptor { + rowspan?: number; + colspan?: number; + series: Partial[]; + axes: Partial[]; + chartType: string; + title?: string; + titleFont?: string; + titleColor?: string; + showLegend?: boolean; + legendFont?: string; + legendColor?: string; + is3d?: boolean; +} +export interface SeriesDescriptor { + plotStyle: string; + name: string; + linesVisible?: boolean; + shapesVisible?: boolean; + gradientVisible?: boolean; + lineColor?: string; + pointLabelFormat?: string; + xToolTipPattern?: string; + yToolTipPattern?: string; + shapeLabel?: string; + shapeSize?: number; + shapeColor?: string; + shape?: string; +} +export interface SourceDescriptor { + axis?: AxisDescriptor; + table: Table; + columnName: string; + columnType: string; +} +export interface AxisDescriptor { + formatType: string; + type: string; + position: string; + log?: boolean; + label?: string; + labelFont?: string; + ticksFont?: string; + formatPattern?: string; + color?: string; + minRange?: number; + maxRange?: number; + minorTicksVisible?: boolean; + majorTicksVisible?: boolean; + minorTickCount?: number; + gapBetweenMajorTicks?: number; + tickLabelAngle?: number; + invert?: boolean; + isTimeAxis?: boolean; +} +export interface Figure extends Evented { + readonly EVENT_UPDATED: string; + readonly EVENT_DISCONNECT: string; + readonly EVENT_RECONNECT: string; + readonly EVENT_RECONNECTFAILED: string; + readonly EVENT_DOWNSAMPLESTARTED: string; + readonly EVENT_DOWNSAMPLEFINISHED: string; + readonly EVENT_DOWNSAMPLEFAILED: string; + readonly EVENT_DOWNSAMPLENEEDED: string; + readonly EVENT_SERIES_ADDED: string; + + /** Given a client-created figure descriptor, generate a figure that can be subscribed to */ + create(figure: Partial): Figure; + + readonly title: string; + readonly titleFont: string; + readonly titleColor: string; + readonly isResizable: boolean; + readonly isDefaultTheme: boolean; + readonly updateInterval: number; + readonly cols: number; + readonly rows: number; + readonly charts: Chart[]; + + /** + * Subscribes to all series in this figure. + * @param forceDisableDownsample optional, can be specified to force downsampling to be disabled + */ + subscribe(forceDisableDownsample?: DownsampleOptions): void; + + /** + * Unsubscribes to all series in this figure. + */ + unsubscribe(): void; + + close(): void; +} + +export interface FigureDataUpdatedEvent { + /** + * The series instances which were affected by this event and need to be updated. + */ + readonly series: Series[]; + + /** + * Reads data out for this series from the event which just occurred for the given series. + * The array returned by this method will be cached and reused to minimize the garbage + * created, and to reduce processing time in handling updates. + * + * The pattern when using this is to iterate over the series which this event affects, and + * for each series, iterate over each source. For each series+source, pass them in, along + * with the optional mapping function, and get back the array of data across the entire plot. + * + * @param series + * @param sourceType + * @param mapFn + */ + getArray( + series: Series, + sourceType: SourceType, + mapFn?: MapFn + ): O[]; +} + +export interface MapFn { + (input: I): O; +} + +export interface DownsampleOptions { + readonly DEFAULT: DownsampleOptions; + readonly DISABLE: DownsampleOptions; +} + +export interface SourceType { + readonly X: SourceType; + readonly Y: SourceType; + readonly Z: SourceType; + readonly X_LOW: SourceType; + readonly X_HIGH: SourceType; + readonly Y_LOW: SourceType; + readonly Y_HIGH: SourceType; + readonly TIME: SourceType; + readonly OPEN: SourceType; + readonly HIGH: SourceType; + readonly LOW: SourceType; + readonly CLOSE: SourceType; + readonly SHAPE: SourceType; + readonly SIZE: SourceType; + readonly LABEL: SourceType; + readonly COLOR: SourceType; + readonly PARENT: SourceType; + readonly HOVER_TEXT: SourceType; + readonly TEXT: SourceType; +} +export interface ChartType { + readonly XY: ChartType; + readonly PIE: ChartType; + readonly OHLC: ChartType; + readonly CATEGORY: ChartType; + readonly XYZ: ChartType; + readonly CATEGORY_3D: ChartType; +} +export interface SeriesPlotStyle { + readonly BAR: SeriesPlotStyle; + readonly STACKED_BAR: SeriesPlotStyle; + readonly LINE: SeriesPlotStyle; + readonly AREA: SeriesPlotStyle; + readonly STACKED_AREA: SeriesPlotStyle; + readonly PIE: SeriesPlotStyle; + readonly HISTOGRAM: SeriesPlotStyle; + readonly OHLC: SeriesPlotStyle; + readonly SCATTER: SeriesPlotStyle; + readonly STEP: SeriesPlotStyle; + readonly ERROR_BAR: SeriesPlotStyle; + readonly TREEMAP: SeriesPlotStyle; +} +export interface AxisFormatType { + readonly CATEGORY: AxisFormatType; + readonly NUMBER: AxisFormatType; +} +export interface AxisType { + readonly X: AxisType; + readonly Y: AxisType; + readonly Z: AxisType; + readonly SHAPE: AxisType; + readonly SIZE: AxisType; + readonly LABEL: AxisType; + readonly COLOR: AxisType; +} +export interface AxisPosition { + readonly TOP: AxisPosition; + readonly BOTTOM: AxisPosition; + readonly LEFT: AxisPosition; + readonly RIGHT: AxisPosition; + readonly NONE: AxisPosition; +} + +export interface Chart extends Evented { + readonly EVENT_SERIES_ADDED: string; + + readonly row: number; + readonly column: number; + readonly colspan: number; + readonly rowspan: number; + readonly chartType: ChartType; + readonly title: string; + readonly titleFont: string; + readonly titleColor: string; + readonly showLegend: boolean; + readonly legendFont: string; + readonly legendColor: string; + readonly is3d: boolean; + readonly series: Series[]; + readonly multiSeries: MultiSeries[]; + readonly axes: Axis[]; +} + +export interface Series { + readonly plotStyle: SeriesPlotStyle; + readonly name: string; + readonly isLinesVisible: boolean | null; + readonly isShapesVisible: boolean | null; + readonly isGradientVisible: boolean; + readonly lineColor: string; + readonly pointLabelFormat: string; + readonly xToolTipPattern: string; + readonly yToolTipPattern: string; + readonly shapeLabel: string; + readonly shapeSize: number; + readonly shape: string; + readonly shapeColor: string; + readonly sources: SeriesDataSource[]; + readonly multiSeries: MultiSeries; + readonly oneClick: OneClick; + + subscribe(downsampleOptions?: DownsampleOptions): void; + unsubscribe(): void; +} + +export interface MultiSeries { + readonly plotStyle: SeriesPlotStyle; + readonly name: string; +} + +export interface BusinessPeriod { + open: string; + close: string; +} + +export interface LocalDateWrapper { + toString: () => string; +} +export interface Holiday { + date: LocalDateWrapper; + businessPeriods: BusinessPeriod[]; +} + +export interface BusinessCalendar { + getName: () => string; + timeZone: TimeZone; + businessPeriods: BusinessPeriod[]; + businessDays: string[]; + holidays: Holiday[]; +} + +export interface Axis { + readonly id: string; + readonly formatType: AxisFormatType; + readonly type: AxisType; + readonly position: AxisPosition; + readonly log: boolean; + readonly label: string; + readonly labelFont: string; + readonly ticksFont: string; + readonly formatPattern: string; + readonly minRange: number; + readonly maxRange: number; + readonly isMinorTicksVisible: boolean; + readonly isMajorTicksVisible: boolean; + readonly minorTickCount: number; + readonly gapBetweenMajorTicks: number; + readonly majorTickLocations: number[]; + readonly tickLabelAngle: number; + readonly isInvert: boolean; + readonly isTimeAxis: boolean; + + readonly FORMAT_TYPE_NUMBER: unknown; + readonly businessCalendar: BusinessCalendar; + + /** + * Indicate the density and range of data that the UI needs for this axis, across any series which + * draws on this axis. Ignored for non-time series data, for non-line series. + * @param pixelCount the approx number of pixels wide + * @param min the optional minimum value visible on this axis - even if specified, smaller values may + * be returned, to ensure that lines drawn off the screen. + * @param max the optional max value visible on this axis. If min is specified, max is also expected. + */ + range(pixelCount?: number, min?: any, max?: any): void; +} + +export interface SeriesDataSource { + readonly axis: Axis; + readonly type: SourceType; + readonly columnType: string; +} + +export interface OneClick { + readonly columns: { name: string; type: string }[]; + readonly requireAllFiltersToDisplay: boolean; + + setValueForColumn(columnName: string, value: any): void; + getValueForColumn(columnName: string): any; +} + +export interface Column { + /** + * @deprecated + */ + readonly index: number; + + readonly type: string; + readonly name: string; + readonly description: string; + readonly constituentType: string; + + readonly isPartitionColumn: boolean; + + filter(): FilterValue; + sort(): Sort; + + formatColor(expression: string): CustomColumn; + formatRowColor(expression: string): CustomColumn; +} + +export interface CustomColumn { + readonly type: string; + readonly name: string; + readonly expression: string; +} + +export interface FilterValueStatic { + ofString(input: unknown): FilterValue; + ofNumber(input: unknown): FilterValue; + ofBoolean(input: unknown): FilterValue; +} +export interface FilterValue { + eq(value: FilterValue): FilterCondition; + eqIgnoreCase(value: FilterValue): FilterCondition; + notEq(value: FilterValue): FilterCondition; + notEqIgnoreCase(value: FilterValue): FilterCondition; + greaterThan(value: FilterValue): FilterCondition; + lessThan(value: FilterValue): FilterCondition; + greaterThanOrEqualTo(value: FilterValue): FilterCondition; + lessThanOrEqualTo(value: FilterValue): FilterCondition; + in(values: FilterValue[]): FilterCondition; + inIgnoreCase(values: FilterValue[]): FilterCondition; + notIn(values: FilterValue[]): FilterCondition; + notInIgnoreCase(values: FilterValue[]): FilterCondition; + contains(value: FilterValue): FilterCondition; + isFalse(): FilterCondition; + isTrue(): FilterCondition; + isNull(): FilterCondition; + invoke(method: string, ...args: FilterValue[]): FilterCondition; +} + +export interface FilterConditionStatic { + invoke(method: string, ...args: FilterValue[]): FilterCondition; + search(value: FilterValue, columns?: FilterValue[]): FilterCondition; +} +export interface FilterCondition { + not(): FilterCondition; + and(first: FilterCondition, ...rest: FilterCondition[]): FilterCondition; + or(first: FilterCondition, ...rest: FilterCondition[]): FilterCondition; + + toString(): string; +} +export interface Sort { + reverse(): Sort; + + readonly column: Column; + readonly direction: 'ASC' | 'DESC' | 'REVERSE' | null; + + readonly isAbs: boolean; + + asc(): Sort; + desc(): Sort; + abs(): Sort; +} + +export interface InputTable { + keys: string[]; + keyColumns: Column[]; + values: string[]; + valueColumns: Column[]; + addRow( + row: Record, + userTimeZone?: string + ): Promise; + addRows( + rows: Record[], + userTimeZone?: string + ): Promise; + addTable(table: Table): Promise; + addTables(tables: Table[]): Promise; + deleteTable(table: Table): Promise; + deleteTables(tables: Table[]): Promise; + table: Table; +} +export interface ColumnGroup { + name: string; + children: string[]; + color?: string; +} + +export interface LayoutHints { + areSavedLayoutsAllowed?: boolean; + frontColumns?: string[]; + backColumns?: string[]; + hiddenColumns?: string[]; + frozenColumns?: string[]; + columnGroups?: ColumnGroup[]; + searchDisplayMode?: keyof SearchDisplayModeStatic; +} + +export interface SearchDisplayModeStatic { + SEARCH_DISPLAY_DEFAULT: 'Default'; + SEARCH_DISPLAY_HIDE: 'Hide'; + SEARCH_DISPLAY_SHOW: 'Show'; +} + +export interface TableStatic { + readonly EVENT_SIZECHANGED: string; + readonly EVENT_UPDATED: string; + readonly EVENT_ROWADDED: string; + readonly EVENT_ROWREMOVED: string; + readonly EVENT_ROWUPDATED: string; + readonly EVENT_SORTCHANGED: string; + readonly EVENT_FILTERCHANGED: string; + readonly EVENT_CUSTOMCOLUMNSCHANGED: string; + readonly EVENT_DISCONNECT: string; + readonly EVENT_RECONNECT: string; + readonly EVENT_RECONNECTFAILED: string; + readonly SIZE_UNCOALESCED: number; + reverse(): Sort; +} + +export interface ClientStatic { + readonly EVENT_REQUEST_FAILED: 'requestfailed'; + readonly EVENT_REQUEST_STARTED: 'requeststarted'; + readonly EVENT_REQUEST_SUCCEEDED: 'requestsucceeded'; +} +export interface Table extends TableTemplate
, TableStatic { + readonly totalSize: number; + + readonly description: string; + + customColumns: string[]; + + readonly layoutHints: LayoutHints; + + readonly isUncoalesced: boolean; + readonly hasInputTable: boolean; + + readonly isClosed: boolean; + readonly pluginName: string; + + applyCustomColumns(columns: (CustomColumn | string)[]): string[]; + + getViewportData(): Promise; + + subscribe(columns: Column[]): TableSubscription; + + selectDistinct(columns: Column[]): Promise
; + copy(): Promise
; + + rollup(config: RollupConfig): Promise; + treeTable(config: TreeTableConfig): Promise; + + inputTable(): Promise; + + freeze(): Promise
; + + snapshot( + rightHandSide: Table, + doInitialSnapshot?: boolean, + stampColumns?: string[] + ): Promise
; + + getColumnStatistics(column: Column): Promise; + + join( + joinType: string, + rightTable: Table, + columnsToMatch: string[], + columnsToAdd?: string[] + ): Promise
; + byExternal(keys: string[], dropKeys?: boolean): Promise; + + fireViewportUpdate(): void; + + seekRow( + startRow: number, + column: Column, + valueType: ValueTypeUnion, + value: unknown, + insensitive?: boolean, + contains?: boolean, + isBackwards?: boolean + ): Promise; +} + +export interface TableViewportSubscription extends Evented { + setViewport(firstRow: number, lastRow: number, columns?: Column[]): void; + getViewportData(): Promise; + snapshot(rows: RangeSet, columns: readonly Column[]): Promise; + close(): void; +} + +export interface ViewportData { + offset: number; + rows: Row[]; + columns: Column[]; +} + +export interface TableSubscription extends Evented { + readonly EVENT_UPDATED: string; + + readonly columns: Column[]; + close(): void; +} + +export interface RangeSet { + ofRange(first: number, last: number): RangeSet; + ofItems(rows: number[]): RangeSet; + ofRanges(ranges: RangeSet[]): RangeSet; + + readonly size: number; + iterator(): JsIterator; +} + +export interface JsIterator { + hasNext(): boolean; + next(): IteratorResult; +} + +export interface LongWrapper { + asNumber(): number; + valueOf(): string; + toString(): string; + ofString(str: string): LongWrapper; +} +export interface DateWrapper extends LongWrapper { + ofJsDate(date: Date): DateWrapper; + asDate(): Date; +} + +export interface TimeZone { + adjustments: number[]; + standardOffset: number; + timeZoneID: string; + id: string; + transitionPoints: number[]; + tzNames: string[]; +} + +export interface i18nTimeZone { + getTimeZone(tzCode: string): TimeZone; +} + +export interface DateTimeFormat { + format( + pattern: string, + date: DateWrapper | Date | number, + timeZone?: TimeZone + ): string; + parse(pattern: string, text: string, timeZone?: TimeZone): DateWrapper; + parseAsDate(pattern: string, text: string): Date; +} + +export interface NumberFormat { + format(pattern: string, number: number): string; + parse(pattern: string, text: string): number; +} + +export interface TableData { + readonly columns: Column[]; + readonly rows: Row[]; + + get(index: number): Row; + get(index: LongWrapper): Row; + + getData(index: number, column: Column): any; + getData(index: LongWrapper, column: Column): any; + + getFormat(index: number, column: Column): Format; + getFormat(index: LongWrapper, column: Column): Format; +} + +export interface UpdateEventData extends TableData { + readonly added: RangeSet; + readonly removed: RangeSet; + readonly modified: RangeSet; + readonly fullIndex: RangeSet; +} + +export interface Row { + readonly index: LongWrapper; + + get(column: Column): any; + + getFormat(column: Column): Format; +} + +export interface Format { + readonly color: string; + readonly backgroundColor: string; + readonly formatString: string; +} + +export interface ColumnStatistics { + readonly statisticsMap: Map; + readonly uniqueValues: Map; + + getType(name: string): string; +} + +export interface TreeTableStatic { + readonly EVENT_UPDATED: string; + readonly EVENT_DISCONNECT: string; + readonly EVENT_RECONNECT: string; + readonly EVENT_RECONNECTFAILED: string; +} + +export interface TableTemplate extends Evented { + readonly size: number; + readonly columns: Column[]; + readonly sort: Sort[]; + readonly filter: FilterCondition[]; + readonly totalsTableConfig: TotalsTableConfig; + + findColumn(name: string): Column; + findColumns(names: string[]): Column[]; + + applySort(sorts: Sort[]): Sort[]; + applyFilter(filters: FilterCondition[]): FilterCondition[]; + selectDistinct(columns: Column[]): Promise
; + + getTotalsTable(config?: TotalsTableConfig): Promise; + getGrandTotalsTable(config?: TotalsTableConfig): Promise; + + setViewport( + firstRow: number, + lastRow: number, + columns?: Column[], + updateIntervalMs?: number + ): TableViewportSubscription; + + copy(): Promise; + close(): void; +} + +export interface TreeTable extends TableTemplate, TreeTableStatic { + readonly isIncludeConstituents: boolean; + readonly groupedColumns: Column[]; + + expand(row: number): void; + expand(row: TreeRow): void; + collapse(row: number): void; + collapse(row: TreeRow): void; + setExpanded( + row: number, + isExpanded: boolean, + expandDescendants?: boolean + ): void; + setExpanded( + row: TreeRow, + isExpanded: boolean, + expandDescendants?: boolean + ): void; + expandAll?(): void; + collapseAll?(): void; + isExpanded(row: number): boolean; + isExpanded(row: TreeRow): boolean; + + getViewportData(): Promise; + + saveExpandedState(): string; + restoreExpandedState(nodesToRestore: string): void; +} +export interface TreeTableData extends TableData { + readonly rows: TreeRow[]; +} +export interface TreeRow extends Row { + readonly isExpanded: boolean; + readonly hasChildren: boolean; + readonly depth: number; +} + +export interface RollupConfig { + groupingColumns: string[] | null; + aggregations: Record | null; + includeConstituents: boolean; + includeOriginalColumns?: boolean; + includeDescriptions: boolean; +} + +export interface TreeTableConfig {} + +export interface TotalsTableConfig { + showTotalsByDefault?: boolean; + showGrandTotalsByDefault?: boolean; + defaultOperation?: string; + groupBy?: readonly string[]; + operationMap: Record; +} + +export interface TotalsTable extends Evented { + readonly size: number; + readonly columns: Column[]; + + readonly sort: Sort[]; + readonly filter: FilterCondition[]; + customColumns: string[]; + + readonly totalsTableConfig: TotalsTableConfig; + + applySort(sorts: Sort[]): Sort[]; + applyFilter(filters: FilterCondition[]): FilterCondition[]; + applyCustomColumns(columns: string[]): string[]; + + setViewport( + firstRow: number, + lastRow: number, + columns?: Column[], + updateIntervalMs?: number + ): void; + getViewportData(): Promise; + + close(): void; +} + +export interface TableMap extends Evented { + readonly size: number; + close(): void; + getKeys(): Promise>; + getTable(key: object): Promise
; +} + +export interface WorkerHeapInfo { + readonly maximumHeapSize: number; + readonly freeMemory: number; + readonly totalHeapSize: number; +} + +export interface QueryConnectable extends Evented { + getWorkerHeapInfo(): Promise; + getConsoleTypes(): Promise; + startSession(type: string): Promise; +} + +export interface IdeConnectionOptions { + authoToken?: string; + serviceId?: string; +} + +export interface IdeConnectionConstructor { + HACK_CONNECTION_FAILURE: string; + new (serverUrl: string, options?: IdeConnectionOptions): IdeConnection; +} + +export interface IdeConnection + extends QueryConnectable, + IdeConnectionConstructor { + close(): void; + running(): Promise; + disconnected(): void; + getObject( + definition: VariableDefinition + ): Promise
; + getObject( + definition: VariableDefinition + ): Promise
; + getObject( + definition: VariableDefinition + ): Promise; + getObject( + definition: VariableDefinition + ): Promise; + getObject(definition: VariableDefinition): Promise; + subscribeToFieldUpdates( + param: (changes: VariableChanges) => void + ): () => void; +} + +export interface ItemDetails { + filename: string; + basename: string; + dirname: string; + type: 'directory' | 'file'; + size: number; + etag?: string; +} + +export interface FileContentsStatic { + blob(blob: Blob): FileContents; + text(...text: string[]): FileContents; + arrayBuffers(...buffers: ArrayBuffer[]): FileContents; +} + +export interface FileContents { + text(): Promise; + arrayBuffer(): Promise; + etag?: string; +} + +export interface LoginOptions { + type: string; + token?: string; +} + +export interface StorageService { + listItems(path: string, glob?: string): Promise; + loadFile(path: string, etag?: string): Promise; + deleteItem(path: string): Promise; + saveFile( + path: string, + contents: FileContents, + allowOverwrite?: boolean + ): Promise; + moveItem(path: string, newPath: string, newFile?: boolean): Promise; + createDirectory(path: string): Promise; +} + +export interface CoreClientContructor { + LOGIN_TYPE_ANONYMOUS: string; + new (serverUrl: string): CoreClient; +} + +export interface CoreClient extends CoreClientContructor { + login(options: LoginOptions): Promise; + getAsIdeConnection(): Promise; + getStorageService(): StorageService; + getServerConfigValues(): [string, string][]; +} diff --git a/packages/jsapi-types/src/index.ts b/packages/jsapi-types/src/index.ts new file mode 100644 index 0000000000..6bb9b0bb90 --- /dev/null +++ b/packages/jsapi-types/src/index.ts @@ -0,0 +1,4 @@ +import type dh from './dh.types'; + +export * from './dh.types'; +export default dh; diff --git a/packages/jsapi-types/tsconfig.json b/packages/jsapi-types/tsconfig.json new file mode 100644 index 0000000000..8c183dff36 --- /dev/null +++ b/packages/jsapi-types/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src/", + "outDir": "dist/" + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], + "exclude": ["node_modules", "src/**/*.test.*", "src/**/__mocks__/*"] +} diff --git a/playwright.config.ts b/playwright.config.ts index d7b6ebb419..fa4bb17bf8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -110,7 +110,7 @@ const config: PlaywrightTestConfig = { webServer: { // Only start the main code-studio server right now // To test embed-grid and embed-chart, should have an array set for `webServer` and run them all separately as there's a port check - command: 'npm run preview:app', + command: 'VITE_PROXY_URL=http://localhost:10000 npm run preview:app', port: 4000, timeout: 60 * 1000, reuseExistingServer: !process.env.CI, diff --git a/tests/update-ci-snapshots/Dockerfile b/tests/update-ci-snapshots/Dockerfile index aa587a1502..9b415d5ae8 100644 --- a/tests/update-ci-snapshots/Dockerfile +++ b/tests/update-ci-snapshots/Dockerfile @@ -27,33 +27,35 @@ SHELL ["/bin/bash", "--login", "-c"] COPY package.json package-lock.json skip.js . COPY packages/babel-preset/package.json packages/babel-preset/ COPY packages/chart/package.json packages/chart/ +COPY packages/code-studio/package.json packages/code-studio/ +COPY packages/components/package.json packages/components/ +COPY packages/console/package.json packages/console/ +COPY packages/dashboard/package.json packages/dashboard/ +COPY packages/dashboard-core-plugins/package.json packages/dashboard-core-plugins/ COPY packages/embed-chart/package.json packages/embed-chart/ COPY packages/embed-grid/package.json packages/embed-grid/ +COPY packages/eslint-config/package.json packages/eslint-config/ +COPY packages/file-explorer/package.json packages/file-explorer/ COPY packages/filters/package.json packages/filters/ COPY packages/golden-layout/package.json packages/golden-layout/ +COPY packages/grid/package.json packages/grid/ +COPY packages/icons/package.json packages/icons/ +COPY packages/iris-grid/package.json packages/iris-grid/ +COPY packages/jsapi-bootstrap/package.json packages/jsapi-bootstrap/ +COPY packages/jsapi-components/package.json packages/jsapi-components/ +COPY packages/jsapi-shim/package.json packages/jsapi-shim/ +COPY packages/jsapi-types/package.json packages/jsapi-types/ +COPY packages/jsapi-utils/package.json packages/jsapi-utils/ +COPY packages/log/package.json packages/log/ COPY packages/mocks/package.json packages/mocks/ +COPY packages/pouch-storage/package.json packages/pouch-storage/ COPY packages/prettier-config/package.json packages/prettier-config/ COPY packages/react-hooks/package.json packages/react-hooks/ -COPY packages/tsconfig/package.json packages/tsconfig/ -COPY packages/utils/package.json packages/utils/ +COPY packages/redux/package.json packages/redux/ COPY packages/storage/package.json packages/storage/ -COPY packages/dashboard/package.json packages/dashboard/ COPY packages/stylelint-config/package.json packages/stylelint-config/ -COPY packages/components/package.json packages/components/ -COPY packages/dashboard-core-plugins/package.json packages/dashboard-core-plugins/ -COPY packages/eslint-config/package.json packages/eslint-config/ -COPY packages/jsapi-shim/package.json packages/jsapi-shim/ -COPY packages/iris-grid/package.json packages/iris-grid/ -COPY packages/icons/package.json packages/icons/ -COPY packages/log/package.json packages/log/ -COPY packages/pouch-storage/package.json packages/pouch-storage/ -COPY packages/file-explorer/package.json packages/file-explorer/ -COPY packages/redux/package.json packages/redux/ -COPY packages/jsapi-components/package.json packages/jsapi-components/ -COPY packages/code-studio/package.json packages/code-studio/ -COPY packages/jsapi-utils/package.json packages/jsapi-utils/ -COPY packages/console/package.json packages/console/ -COPY packages/grid/package.json packages/grid/ +COPY packages/tsconfig/package.json packages/tsconfig/ +COPY packages/utils/package.json packages/utils/ # Disable the postinstall script, or npm ci will try and build and the files won't be there # We don't need the postinstall since we're going to rebuild right after