upgrade react to v18 and other packages

This commit is contained in:
Bikram Choudhury 2025-10-01 17:18:51 +05:30
parent a5c3e6bea0
commit 695729a8b6
30 changed files with 15835 additions and 24094 deletions

23
.vscode/settings.json vendored
View File

@ -1,22 +1,6 @@
// Place your settings in this file to overwrite default and user settings. // Place your settings in this file to overwrite default and user settings.
{ {
"files.exclude": {
".vs": true,
".vscode/**": true,
"*.trx": true,
"**/.DS_Store": true,
"**/.git": true,
"**/.hg": true,
"**/.svn": true,
"built/**": true,
"coverage/**": true,
"libs/**": true,
"node_modules/**": true,
"package-lock.json": true,
"quickstart/**": true,
"test/out/**": true,
"workers/libs/**": true
},
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
@ -24,5 +8,8 @@
"source.organizeImports": "explicit" "source.organizeImports": "explicit"
}, },
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
} }

37956
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,200 +4,270 @@
"description": "Cosmos Explorer", "description": "Cosmos Explorer",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "16.3.0",
"@azure/cosmos": "4.5.0", "@azure/cosmos": "4.5.0",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.10.1",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "4.24.0",
"@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-transform-class-properties": "^7.24.7",
"@babel/plugin-proposal-decorators": "7.12.12", "@babel/plugin-proposal-decorators": "7.28.0",
"@fluentui/react": "8.119.0", "@fluentui/react": "8.123.6",
"@fluentui/react-components": "9.54.2", "@fluentui/react-components": "9.70.0",
"@jupyterlab/services": "6.0.2", "@jupyterlab/services": "7.4.9",
"@jupyterlab/terminal": "3.0.3", "@jupyterlab/terminal": "4.4.9",
"@microsoft/applicationinsights-web": "2.6.1", "@microsoft/applicationinsights-web": "3.3.10",
"@nteract/commutable": "7.5.1", "@nteract/commutable": "7.5.1",
"@nteract/connected-components": "6.8.2", "@nteract/connected-components": "6.9.0",
"@nteract/core": "15.1.9", "@nteract/core": "15.1.9",
"@nteract/data-explorer": "8.0.3", "@nteract/data-explorer": "8.2.12",
"@nteract/directory-listing": "2.0.6", "@nteract/directory-listing": "2.1.0",
"@nteract/dropdown-menu": "1.0.1", "@nteract/dropdown-menu": "1.1.9",
"@nteract/editor": "10.1.12", "@nteract/editor": "10.1.12",
"@nteract/fixtures": "2.3.0", "@nteract/fixtures": "2.3.19",
"@nteract/iron-icons": "1.0.0", "@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0", "@nteract/jupyter-widgets": "4.1.19",
"@nteract/logos": "1.0.0", "@nteract/logos": "1.0.0",
"@nteract/markdown": "4.6.0", "@nteract/markdown": "4.6.2",
"@nteract/monaco-editor": "3.2.2", "@nteract/monaco-editor": "3.2.2",
"@nteract/octicons": "2.0.0", "@nteract/octicons": "2.0.0",
"@nteract/outputs": "3.0.9", "@nteract/outputs": "5.1.14",
"@nteract/presentational-components": "3.0.7", "@nteract/presentational-components": "3.4.12",
"@nteract/stateful-components": "1.7.0", "@nteract/stateful-components": "1.7.15",
"@nteract/styles": "2.0.2", "@nteract/styles": "2.2.11",
"@nteract/transform-geojson": "5.1.8", "@nteract/transform-geojson": "5.1.13",
"@nteract/transform-model-debug": "5.0.1", "@nteract/transform-model-debug": "5.0.1",
"@nteract/transform-plotly": "6.1.6", "@nteract/transform-plotly": "7.0.1",
"@nteract/transform-vdom": "4.0.11", "@nteract/transform-vdom": "4.0.15",
"@nteract/transform-vega": "7.0.6", "@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2", "@octokit/rest": "21.1.1",
"@phosphor/widgets": "1.9.3", "@phosphor/widgets": "1.9.3",
"@testing-library/jest-dom": "6.4.6", "@testing-library/jest-dom": "6.8.0",
"@types/lodash": "4.14.171", "@types/lodash": "4.17.20",
"@types/mkdirp": "1.0.1", "@types/mkdirp": "1.0.2",
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.6.13",
"@xmldom/xmldom": "0.7.13", "@xmldom/xmldom": "0.9.8",
"@xterm/xterm": "5.5.0", "@xterm/xterm": "5.5.0",
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.10.0",
"allotment": "1.20.2", "allotment": "1.20.4",
"applicationinsights": "1.8.0", "applicationinsights": "3.12.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "2.11.2", "canvas": "3.2.0",
"clean-webpack-plugin": "4.0.0", "clean-webpack-plugin": "4.0.0",
"clipboard-copy": "4.0.1", "clipboard-copy": "4.0.1",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "13.0.1",
"crossroads": "0.12.2", "crossroads": "0.12.2",
"css-element-queries": "1.1.1", "css-element-queries": "1.2.3",
"d3": "7.8.5", "d3": "7.9.0",
"datatables.net-colreorder-dt": "1.7.0", "datatables.net-colreorder-dt": "2.1.1",
"datatables.net-dt": "1.13.8", "datatables.net-dt": "2.3.4",
"date-fns": "1.29.0", "date-fns": "4.1.0",
"dayjs": "1.8.19", "dayjs": "1.11.18",
"dom-to-image": "2.6.0", "dom-to-image": "2.6.0",
"dotenv": "8.2.0", "dotenv": "17.2.3",
"eslint-plugin-jest": "27.4.2", "eslint-plugin-jest": "28.14.0",
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.37.5",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5", "html2canvas": "1.4.1",
"i18next": "23.11.5", "i18next": "25.5.2",
"i18next-browser-languagedetector": "6.0.1", "i18next-browser-languagedetector": "8.2.0",
"i18next-http-backend": "1.0.23", "i18next-http-backend": "3.0.2",
"iframe-resizer-react": "1.1.0", "iframe-resizer-react": "5.1.5",
"immer": "9.0.6", "immer": "10.1.3",
"immutable": "4.0.0-rc.12", "immutable": "4.0.0-rc.12",
"is-ci": "2.0.0", "is-ci": "4.1.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery-typeahead": "2.11.1", "jquery-typeahead": "2.11.1",
"jquery-ui-dist": "1.13.2", "jquery-ui-dist": "1.13.3",
"knockout": "3.5.1", "knockout": "3.5.1",
"loader-utils": "2.0.3", "loader-utils": "3.3.1",
"mkdirp": "1.0.4", "mkdirp": "3.0.1",
"monaco-editor": "0.44.0", "monaco-editor": "0.53.0",
"ms": "2.1.3", "ms": "2.1.3",
"p-retry": "6.2.1", "p-retry": "6.2.1",
"patch-package": "8.0.0", "patch-package": "8.0.1",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "3.1.1",
"post-robot": "10.0.42", "post-robot": "10.0.42",
"q": "1.5.1", "q": "2.0.3",
"react": "16.14.0", "react": "18.2.0",
"react-animate-height": "2.0.8", "react-animate-height": "3.2.3",
"react-dnd": "14.0.2", "react-dnd": "16.0.1",
"react-dnd-html5-backend": "14.0.0", "react-dnd-html5-backend": "16.0.1",
"react-dom": "16.14.0", "react-dom": "18.2.0",
"react-hotkeys": "2.0.0", "react-hotkeys": "2.0.0",
"react-i18next": "14.1.2", "react-i18next": "16.0.0",
"react-notification-system": "0.2.17", "react-notification-system": "0.2.17",
"react-redux": "7.1.3", "react-redux": "7.2.9",
"react-splitter-layout": "4.0.0", "react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1", "react-string-format": "1.2.0",
"react-window": "1.8.10", "react-window": "1.8.10",
"react-youtube": "9.0.1", "react-youtube": "10.1.0",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.2.2",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.21",
"sanitize-html": "2.3.3", "sanitize-html": "2.17.0",
"shell-quote": "1.7.3", "shell-quote": "1.8.3",
"styled-components": "5.0.1", "styled-components": "6.1.19",
"swr": "0.4.0", "swr": "2.3.6",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.14",
"tinykeys": "2.1.0", "tinykeys": "3.0.0",
"underscore": "1.12.1", "underscore": "1.13.7",
"utility-types": "3.10.0", "utility-types": "3.11.0",
"zustand": "3.5.0" "zustand": "5.0.8"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.24.7", "@babel/core": "7.28.4",
"@babel/preset-env": "7.24.7", "@babel/preset-env": "7.28.3",
"@babel/preset-react": "7.24.7", "@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.24.7", "@babel/preset-typescript": "7.27.1",
"@playwright/test": "1.49.1", "@playwright/test": "1.55.1",
"@testing-library/react": "11.2.3", "@testing-library/react": "16.3.0",
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.9",
"@types/codemirror": "0.0.56", "@types/codemirror": "5.60.16",
"@types/crossroads": "0.0.30", "@types/crossroads": "0.0.33",
"@types/d3": "5.9.2", "@types/d3": "7.4.3",
"@types/datatables.net": "1.10.28", "@types/datatables.net": "1.10.28",
"@types/datatables.net-colreorder": "1.4.5", "@types/datatables.net-colreorder": "1.4.5",
"@types/dom-to-image": "2.6.2", "@types/dom-to-image": "2.6.7",
"@types/enzyme": "3.10.12", "@types/enzyme": "3.10.19",
"@types/enzyme-adapter-react-16": "1.0.9", "@types/enzyme-adapter-react-16": "1.0.9",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.35",
"@types/jest": "29.5.12", "@types/jest": "30.0.0",
"@types/jquery": "3.5.29", "@types/jquery": "3.5.33",
"@types/node": "12.11.1", "@types/node": "24.6.0",
"@types/post-robot": "10.0.1", "@types/post-robot": "10.0.6",
"@types/q": "1.5.1", "@types/q": "1.5.8",
"@types/react": "17.0.44", "@types/react": "18.3.7",
"@types/react-dom": "17.0.15", "@types/react-dom": "18.3.7",
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.46",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.34",
"@types/react-splitter-layout": "3.0.1", "@types/react-splitter-layout": "4.0.0",
"@types/react-window": "1.8.8", "@types/react-window": "1.8.8",
"@types/sanitize-html": "1.27.2", "@types/sanitize-html": "2.16.0",
"@types/sinon": "2.3.3", "@types/sinon": "17.0.4",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.34",
"@types/underscore": "1.7.36", "@types/underscore": "1.13.0",
"@types/youtube-player": "5.5.6", "@types/youtube-player": "5.5.11",
"@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/eslint-plugin": "8.45.0",
"@typescript-eslint/parser": "6.7.4", "@typescript-eslint/parser": "8.45.0",
"@webpack-cli/serve": "2.0.5", "@webpack-cli/serve": "3.0.1",
"babel-jest": "29.7.0", "babel-jest": "30.2.0",
"babel-loader": "8.1.0", "babel-loader": "10.0.0",
"buffer": "5.1.0", "buffer": "6.0.3",
"case-sensitive-paths-webpack-plugin": "2.4.0", "case-sensitive-paths-webpack-plugin": "2.4.0",
"create-file-webpack": "1.0.2", "create-file-webpack": "1.0.2",
"css-loader": "6.8.1", "css-loader": "7.1.2",
"enzyme": "3.11.0", "enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.8", "enzyme-adapter-react-16": "1.15.8",
"enzyme-to-json": "3.6.2", "enzyme-to-json": "3.6.2",
"eslint": "8.50.0", "eslint": "9.36.0",
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "5.2.0",
"fast-glob": "3.2.5", "fast-glob": "3.3.3",
"fs-extra": "7.0.0", "fs-extra": "11.3.2",
"html-inline-css-webpack-plugin": "1.11.2", "html-inline-css-webpack-plugin": "1.11.2",
"html-loader": "5.0.0", "html-loader": "5.1.0",
"html-webpack-plugin": "5.5.3", "html-webpack-plugin": "5.6.4",
"jest": "29.7.0", "jest": "30.2.0",
"jest-canvas-mock": "2.5.2", "jest-canvas-mock": "2.5.2",
"jest-circus": "29.7.0", "jest-circus": "30.2.0",
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "30.2.0",
"jest-html-loader": "1.0.0", "jest-html-loader": "1.0.0",
"jest-react-hooks-shallow": "1.5.1", "jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "3.0.2", "jest-trx-results-processor": "3.0.2",
"less": "3.8.1", "less": "4.4.1",
"less-loader": "11.1.3", "less-loader": "12.3.0",
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "2.1.0", "mini-css-extract-plugin": "2.9.4",
"monaco-editor-webpack-plugin": "7.1.0", "monaco-editor-webpack-plugin": "7.1.0",
"node-fetch": "2.6.7", "node-fetch": "3.3.2",
"prettier": "3.0.3", "prettier": "3.6.2",
"process": "0.11.10", "process": "0.11.10",
"querystring-es3": "0.2.1", "querystring-es3": "0.2.1",
"raw-loader": "0.5.1", "raw-loader": "4.0.2",
"react-dev-utils": "12.0.1", "react-dev-utils": "12.0.1",
"rimraf": "3.0.0", "rimraf": "5.0.10",
"sinon": "3.2.1", "sinon": "21.0.0",
"style-loader": "0.23.0", "style-loader": "4.0.0",
"ts-loader": "9.2.4", "ts-loader": "9.5.4",
"typedoc": "0.26.2", "typedoc": "0.28.13",
"typescript": "4.9.5", "typescript": "5.9.2",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"wait-on": "4.0.2", "wait-on": "8.0.5",
"webpack": "5.88.2", "webpack": "5.102.0",
"webpack-bundle-analyzer": "4.9.1", "webpack-bundle-analyzer": "4.10.2",
"webpack-cli": "5.1.4", "webpack-cli": "6.0.1",
"webpack-dev-server": "4.15.2" "webpack-dev-server": "5.2.2"
},
"overrides": {
"@nteract/connected-components": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/core": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/data-explorer": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/dropdown-menu": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/editor": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/iron-icons": {
"react": "18.2.0"
},
"@nteract/jupyter-widgets": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/logos": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/markdown": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/monaco-editor": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/octicons": {
"react": "18.2.0"
},
"@nteract/outputs": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/presentational-components": {
"react": "18.2.0"
},
"@nteract/stateful-components": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"@nteract/transform-geojson": {
"react": "18.2.0"
},
"@nteract/transform-model-debug": {
"react": "18.2.0"
},
"@nteract/transform-plotly": {
"react": "18.2.0"
},
"@nteract/transform-vdom": {
"react": "18.2.0"
},
"@nteract/transform-vega": {
"react": "18.2.0"
}
}, },
"scripts": { "scripts": {
"postinstall": "patch-package", "postinstall": "patch-package",

View File

@ -6,9 +6,9 @@ index e5dc283..1930c2b 100644
/// <reference types="jquery" /> /// <reference types="jquery" />
-import DataTables, {Api} from 'datatables.net'; -import DataTables, {Api, ColumnSelector} from 'datatables.net';
+import DataTables, { Api } from 'datatables.net'; +import DataTables, { Api, ColumnSelector } from 'datatables.net';
export default DataTables; export default DataTables;
@@ -40,6 +40,8 @@ declare module 'datatables.net' { @@ -40,6 +40,8 @@ declare module 'datatables.net' {
@ -17,6 +17,6 @@ index e5dc283..1930c2b 100644
*/ */
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type. + // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
+ // @ts-ignore + // @ts-ignore
new (dt: Api<any>, settings: boolean | ConfigColReorder); new (dt: Api<any>, settings: boolean | ConfigColReorder): DataTablesStatic['ColReorder'];
/** /**

View File

@ -16,7 +16,7 @@ import {
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import React, { FC, useEffect } from "react"; import React, { FC, useEffect } from "react";
import create, { UseStore } from "zustand"; import { create } from "zustand";
export interface DialogState { export interface DialogState {
visible: boolean; visible: boolean;
@ -38,7 +38,7 @@ export interface DialogState {
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void; showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void;
} }
export const useDialog: UseStore<DialogState> = create((set, get) => ({ export const useDialog = create<DialogState>((set, get) => ({
visible: false, visible: false,
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })), openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
closeDialog: () => closeDialog: () =>

View File

@ -23,7 +23,7 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
import _ from "underscore"; import _ from "underscore";
import shallow from "zustand/shallow"; import { shallow } from "zustand/shallow";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
@ -112,8 +112,8 @@ export default class Explorer {
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id); this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
useNotebook.subscribe( useNotebook.subscribe(
() => this.refreshCommandBarButtons(),
(state) => state.isNotebooksEnabledForAccount, (state) => state.isNotebooksEnabledForAccount,
() => this.refreshCommandBarButtons(),
); );
this.queriesClient = new QueriesClient(this); this.queriesClient = new QueriesClient(this);
@ -136,13 +136,13 @@ export default class Explorer {
}); });
useTabs.subscribe( useTabs.subscribe(
(state) => state.openedTabs,
(openedTabs: TabsBase[]) => { (openedTabs: TabsBase[]) => {
if (openedTabs.length === 0) { if (openedTabs.length === 0) {
useSelectedNode.getState().setSelectedNode(undefined); useSelectedNode.getState().setSelectedNode(undefined);
useCommandBar.getState().setContextButtons([]); useCommandBar.getState().setContextButtons([]);
} }
}, }
(state) => state.openedTabs,
); );
this.isTabsContentExpanded = ko.observable(false); this.isTabsContentExpanded = ko.observable(false);
@ -170,9 +170,9 @@ export default class Explorer {
); );
useNotebook.subscribe( useNotebook.subscribe(
async () => this.initiateAndRefreshNotebookList(),
(state) => [state.isNotebookEnabled, state.isRefreshed], (state) => [state.isNotebookEnabled, state.isRefreshed],
shallow, async () => this.initiateAndRefreshNotebookList(),
{ equalityFn: shallow },
); );
this.resourceTree = new ResourceTreeAdapter(this); this.resourceTree = new ResourceTreeAdapter(this);

View File

@ -10,7 +10,7 @@ import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import { isFabric } from "Platform/Fabric/FabricUtil"; import { isFabric } from "Platform/Fabric/FabricUtil";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import * as React from "react"; import * as React from "react";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants"; import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants"; import { StyleConstants } from "../../../Common/StyleConstants";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
@ -30,7 +30,7 @@ export interface CommandBarStore {
setIsHidden: (isHidden: boolean) => void; setIsHidden: (isHidden: boolean) => void;
} }
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({ export const useCommandBar = create<CommandBarStore>((set) => ({
contextButtons: [] as CommandButtonComponentProps[], contextButtons: [] as CommandButtonComponentProps[],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })), setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false, isHidden: false,

View File

@ -1,5 +1,5 @@
import { AppState, ContentRef, selectors } from "@nteract/core"; import { AppState, ContentRef, selectors } from "@nteract/core";
import distanceInWordsToNow from "date-fns/distance_in_words_to_now"; import { formatDistanceToNow } from "date-fns";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styled from "styled-components"; import styled from "styled-components";
@ -59,7 +59,7 @@ export class StatusBar extends React.Component<Props> {
<Bar data-test="notebookStatusBar"> <Bar data-test="notebookStatusBar">
<RightStatus> <RightStatus>
{this.props.lastSaved ? ( {this.props.lastSaved ? (
<p data-test="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p> <p data-test="saveStatus"> Last saved {formatDistanceToNow(this.props.lastSaved)} ago </p>
) : ( ) : (
<p> Not saved yet </p> <p> Not saved yet </p>
)} )}

View File

@ -1,7 +1,8 @@
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { PhoenixClient } from "Phoenix/PhoenixClient"; import { PhoenixClient } from "Phoenix/PhoenixClient";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { subscribeWithSelector } from 'zustand/middleware';
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants"; import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
@ -66,270 +67,274 @@ interface NotebookState {
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void; setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
} }
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({ export const useNotebook = create<NotebookState>()(
isNotebookEnabled: false, subscribeWithSelector(
isNotebooksEnabledForAccount: false, (set, get) => ({
notebookServerInfo: { isNotebookEnabled: false,
notebookServerEndpoint: undefined, isNotebooksEnabledForAccount: false,
authToken: undefined, notebookServerInfo: {
forwardingId: undefined, notebookServerEndpoint: "",
}, authToken: "",
sparkClusterConnectionInfo: { forwardingId: "",
userName: undefined, },
password: undefined, sparkClusterConnectionInfo: {
endpoints: [], userName: "",
}, password: "",
isSynapseLinkUpdating: false, endpoints: [] as DataModels.SparkClusterEndpoint[],
memoryUsageInfo: undefined, },
isShellEnabled: false, isSynapseLinkUpdating: false,
notebookBasePath: Constants.Notebook.defaultBasePath, memoryUsageInfo: undefined as DataModels.MemoryUsageInfo,
isInitializingNotebooks: false, isShellEnabled: false,
myNotebooksContentRoot: undefined, notebookBasePath: Constants.Notebook.defaultBasePath,
gitHubNotebooksContentRoot: undefined, isInitializingNotebooks: false,
galleryContentRoot: undefined, myNotebooksContentRoot: undefined as NotebookContentItem,
connectionInfo: { gitHubNotebooksContentRoot: undefined as NotebookContentItem,
status: ConnectionStatusType.Connect, galleryContentRoot: undefined as NotebookContentItem,
}, connectionInfo: {
notebookFolderName: undefined, status: ConnectionStatusType.Connect,
isAllocating: false, },
isRefreshed: false, notebookFolderName: "",
containerStatus: { isAllocating: false,
status: undefined, isRefreshed: false,
durationLeftInMinutes: undefined, containerStatus: {
phoenixServerInfo: undefined, status: undefined,
}, durationLeftInMinutes: undefined,
isPhoenixNotebooks: undefined, phoenixServerInfo: undefined,
isPhoenixFeatures: undefined, } as ContainerInfo,
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }), isPhoenixNotebooks: undefined as boolean,
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }), isPhoenixFeatures: undefined as boolean,
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
set({ notebookServerInfo }), setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) => setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
set({ sparkClusterConnectionInfo }), set({ notebookServerInfo }),
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }), setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }), set({ sparkClusterConnectionInfo }),
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }), setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }), setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }), setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => { setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
await get().getPhoenixStatus(); setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
const { databaseAccount, authType } = userContext; refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
if ( await get().getPhoenixStatus();
authType === AuthType.EncryptedToken || const { databaseAccount, authType } = userContext;
authType === AuthType.ResourceToken || if (
authType === AuthType.MasterKey authType === AuthType.EncryptedToken ||
) { authType === AuthType.ResourceToken ||
set({ isNotebooksEnabledForAccount: false }); authType === AuthType.MasterKey
return; ) {
} set({ isNotebooksEnabledForAccount: false });
return;
const firstWriteLocation =
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
? databaseAccount?.location
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
const authorizationHeader = getAuthorizationHeader();
try {
const response = await fetch(disallowedLocationsUri, {
method: "POST",
body: JSON.stringify({
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
}),
headers: {
[authorizationHeader.header]: authorizationHeader.token,
[Constants.HttpHeaders.contentType]: "application/json",
},
});
if (!response.ok) {
throw new Error("Failed to fetch disallowed locations");
}
const disallowedLocations: string[] = await response.json();
if (!disallowedLocations) {
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
set({ isNotebooksEnabledForAccount: true });
return;
}
// firstWriteLocation should not be disallowed
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
} catch (error) {
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
set({ isNotebooksEnabledForAccount: false });
}
},
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
const currentItem = root || get().myNotebooksContentRoot;
if (currentItem) {
if (currentItem.path === item.path && currentItem.name === item.name) {
return currentItem;
}
if (currentItem.children) {
for (const childItem of currentItem.children) {
const result = get().findItem(childItem, item);
if (result) {
return result;
}
} }
}
}
return undefined; const firstWriteLocation =
}, userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => { ? databaseAccount?.location
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot); : databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
const parentItem = get().findItem(root, parent); const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
item.parent = parentItem; const authorizationHeader = getAuthorizationHeader();
if (parentItem.children) { try {
parentItem.children.push(item); const response = await fetch(disallowedLocationsUri, {
} else { method: "POST",
parentItem.children = [item]; body: JSON.stringify({
} resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root }); }),
}, headers: {
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => { [authorizationHeader.header]: authorizationHeader.token,
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot); [Constants.HttpHeaders.contentType]: "application/json",
const parentItem = get().findItem(root, item.parent); },
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
parentItem.children.push(item);
item.parent = parentItem;
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, item.parent);
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
set({ notebookFolderName });
const myNotebooksContentRoot = {
name: get().notebookFolderName,
path: get().notebookBasePath,
type: NotebookContentItemType.Directory,
};
const galleryContentRoot = {
name: "Gallery",
path: "Gallery",
type: NotebookContentItemType.File,
};
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
? {
name: "GitHub repos",
path: "PsuedoDir",
type: NotebookContentItemType.Directory,
}
: undefined;
set({
myNotebooksContentRoot,
galleryContentRoot,
gitHubNotebooksContentRoot,
});
if (get().notebookServerInfo?.notebookServerEndpoint) {
const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
set({ myNotebooksContentRoot: updatedRoot });
if (updatedRoot?.children) {
// Count 1st generation children (tree is lazy-loaded)
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
updatedRoot.children.forEach((notebookItem) => {
switch (notebookItem.type) {
case NotebookContentItemType.File:
nodeCounts.files++;
break;
case NotebookContentItemType.Directory:
nodeCounts.directories++;
break;
case NotebookContentItemType.Notebook:
nodeCounts.notebooks++;
break;
default:
break;
}
});
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
}
}
},
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
if (gitHubNotebooksContentRoot) {
gitHubNotebooksContentRoot.children = [];
pinnedRepos?.forEach((pinnedRepo) => {
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
const repoTreeItem: NotebookContentItem = {
name: repoFullName,
path: "PsuedoDir",
type: NotebookContentItemType.Directory,
children: [],
parent: gitHubNotebooksContentRoot,
};
pinnedRepo.branches.forEach((branch) => {
repoTreeItem.children.push({
name: branch.name,
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
type: NotebookContentItemType.Directory,
parent: repoTreeItem,
}); });
if (!response.ok) {
throw new Error("Failed to fetch disallowed locations");
}
const disallowedLocations: string[] = await response.json();
if (!disallowedLocations) {
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
set({ isNotebooksEnabledForAccount: true });
return;
}
// firstWriteLocation should not be disallowed
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
} catch (error) {
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
set({ isNotebooksEnabledForAccount: false });
}
},
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
const currentItem = root || get().myNotebooksContentRoot;
if (currentItem) {
if (currentItem.path === item.path && currentItem.name === item.name) {
return currentItem;
}
if (currentItem.children) {
for (const childItem of currentItem.children) {
const result = get().findItem(childItem, item);
if (result) {
return result;
}
}
}
}
return undefined;
},
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, parent);
item.parent = parentItem;
if (parentItem.children) {
parentItem.children.push(item);
} else {
parentItem.children = [item];
}
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, item.parent);
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
parentItem.children.push(item);
item.parent = parentItem;
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, item.parent);
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
set({ notebookFolderName });
const myNotebooksContentRoot = {
name: get().notebookFolderName,
path: get().notebookBasePath,
type: NotebookContentItemType.Directory,
};
const galleryContentRoot = {
name: "Gallery",
path: "Gallery",
type: NotebookContentItemType.File,
};
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
? {
name: "GitHub repos",
path: "PsuedoDir",
type: NotebookContentItemType.Directory,
}
: undefined;
set({
myNotebooksContentRoot,
galleryContentRoot,
gitHubNotebooksContentRoot,
}); });
gitHubNotebooksContentRoot.children.push(repoTreeItem); if (get().notebookServerInfo?.notebookServerEndpoint) {
}); const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
set({ myNotebooksContentRoot: updatedRoot });
set({ gitHubNotebooksContentRoot }); if (updatedRoot?.children) {
} // Count 1st generation children (tree is lazy-loaded)
}, const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }), updatedRoot.children.forEach((notebookItem) => {
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }), switch (notebookItem.type) {
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => { case NotebookContentItemType.File:
useTabs.getState().closeAllNotebookTabs(true); nodeCounts.files++;
useNotebook.getState().setConnectionInfo(connectionStatus); break;
useNotebook.getState().setNotebookServerInfo(undefined); case NotebookContentItemType.Directory:
useNotebook.getState().setIsAllocating(false); nodeCounts.directories++;
useNotebook.getState().setContainerStatus({ break;
status: undefined, case NotebookContentItemType.Notebook:
durationLeftInMinutes: undefined, nodeCounts.notebooks++;
phoenixServerInfo: undefined, break;
}); default:
}, break;
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }), }
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }), });
getPhoenixStatus: async () => { TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) { }
let isPhoenixNotebooks = false;
let isPhoenixFeatures = false;
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
isPhoenixFeatures =
isPublicInternetAllowed &&
// phoenix needs to be enabled for Postgres and VCoreMongo accounts since the PSQL and mongo shell requires phoenix containers
(userContext.features.phoenixFeatures === true ||
userContext.apiType === "Postgres" ||
userContext.apiType === "VCoreMongo");
} else {
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
} }
} else { },
isPhoenixNotebooks = isPhoenixFeatures = false; initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
} const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
set({ isPhoenixNotebooks: isPhoenixNotebooks }); if (gitHubNotebooksContentRoot) {
set({ isPhoenixFeatures: isPhoenixFeatures }); gitHubNotebooksContentRoot.children = [];
} pinnedRepos?.forEach((pinnedRepo) => {
}, const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }), const repoTreeItem: NotebookContentItem = {
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }), name: repoFullName,
})); path: "PsuedoDir",
type: NotebookContentItemType.Directory,
children: [],
parent: gitHubNotebooksContentRoot,
};
pinnedRepo.branches.forEach((branch) => {
repoTreeItem.children.push({
name: branch.name,
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
type: NotebookContentItemType.Directory,
parent: repoTreeItem,
});
});
gitHubNotebooksContentRoot.children.push(repoTreeItem);
});
set({ gitHubNotebooksContentRoot });
}
},
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
useTabs.getState().closeAllNotebookTabs(true);
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo(undefined);
useNotebook.getState().setIsAllocating(false);
useNotebook.getState().setContainerStatus({
status: undefined,
durationLeftInMinutes: undefined,
phoenixServerInfo: undefined,
});
},
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
getPhoenixStatus: async () => {
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
let isPhoenixNotebooks = false;
let isPhoenixFeatures = false;
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
isPhoenixFeatures =
isPublicInternetAllowed &&
// phoenix needs to be enabled for Postgres and VCoreMongo accounts since the PSQL and mongo shell requires phoenix containers
(userContext.features.phoenixFeatures === true ||
userContext.apiType === "Postgres" ||
userContext.apiType === "VCoreMongo");
} else {
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
}
} else {
isPhoenixNotebooks = isPhoenixFeatures = false;
}
set({ isPhoenixNotebooks: isPhoenixNotebooks });
set({ isPhoenixFeatures: isPhoenixFeatures });
}
},
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
})
)
);

View File

@ -51,7 +51,7 @@ import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
@ -65,8 +65,6 @@ export interface DataPlaneRbacState {
setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void; setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void;
} }
type DataPlaneRbacStore = UseStore<Partial<DataPlaneRbacState>>;
const useStyles = makeStyles({ const useStyles = makeStyles({
bulletList: { bulletList: {
listStyleType: "disc", listStyleType: "disc",
@ -100,7 +98,7 @@ const useStyles = makeStyles({
}, },
}); });
export const useDataPlaneRbac: DataPlaneRbacStore = create(() => ({ export const useDataPlaneRbac = create<Partial<DataPlaneRbacState>>(() => ({
dataPlaneRbacEnabled: false, dataPlaneRbacEnabled: false,
})); }));

View File

@ -1,12 +1,14 @@
import { MinimalQueryIterator } from "Common/IteratorUtilities"; import { MinimalQueryIterator } from "Common/IteratorUtilities";
import QueryError from "Common/QueryError"; import QueryError from "Common/QueryError";
import * as DataModels from "Contracts/DataModels";
import { QueryResults } from "Contracts/ViewModels"; import { QueryResults } from "Contracts/ViewModels";
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { guid } from "Explorer/Tables/Utilities"; import { guid } from "Explorer/Tables/Utilities";
import { QueryCopilotState } from "hooks/useQueryCopilot"; import { QueryCopilotState } from "hooks/useQueryCopilot";
import React, { createContext, useContext, useState } from "react"; import React, { createContext, useContext, useState } from "react";
import create from "zustand"; import { create } from "zustand";
const context = createContext(null); const context = createContext(null);
const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context); const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context);
@ -24,12 +26,12 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
isGeneratingQuery: false, isGeneratingQuery: false,
isGeneratingExplanation: false, isGeneratingExplanation: false,
isExecuting: false, isExecuting: false,
dislikeQuery: undefined, dislikeQuery: undefined as boolean,
showCallout: false, showCallout: false,
showSamplePrompts: false, showSamplePrompts: false,
queryIterator: undefined, queryIterator: undefined as MinimalQueryIterator,
queryResults: undefined, queryResults: undefined as QueryResults,
errors: [], errors: [] as QueryError[],
isSamplePromptsOpen: false, isSamplePromptsOpen: false,
showPromptTeachingBubble: true, showPromptTeachingBubble: true,
showDeletePopup: false, showDeletePopup: false,
@ -41,7 +43,7 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
wasCopilotUsed: false, wasCopilotUsed: false,
showWelcomeSidebar: true, showWelcomeSidebar: true,
showCopilotSidebar: false, showCopilotSidebar: false,
chatMessages: [], chatMessages: [] as CopilotMessage[],
shouldIncludeInMessages: true, shouldIncludeInMessages: true,
showExplanationBubble: false, showExplanationBubble: false,
isAllocatingContainer: false, isAllocatingContainer: false,
@ -86,7 +88,7 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
}, },
resetQueryCopilotStates: () => { resetQueryCopilotStates: () => {
set((state) => ({ set((state: QueryCopilotState) => ({
...state, ...state,
generatedQuery: "", generatedQuery: "",
likeQuery: false, likeQuery: false,
@ -99,11 +101,11 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
isGeneratingQuery: false, isGeneratingQuery: false,
isGeneratingExplanation: false, isGeneratingExplanation: false,
isExecuting: false, isExecuting: false,
dislikeQuery: undefined, dislikeQuery: undefined as boolean,
showCallout: false, showCallout: false,
showSamplePrompts: false, showSamplePrompts: false,
queryIterator: undefined, queryIterator: undefined as MinimalQueryIterator,
queryResults: undefined, queryResults: undefined as QueryResults,
errorMessage: "", errorMessage: "",
isSamplePromptsOpen: false, isSamplePromptsOpen: false,
showPromptTeachingBubble: true, showPromptTeachingBubble: true,
@ -115,19 +117,19 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
generatedQueryComments: "", generatedQueryComments: "",
wasCopilotUsed: false, wasCopilotUsed: false,
showCopilotSidebar: false, showCopilotSidebar: false,
chatMessages: [], chatMessages: [] as CopilotMessage[],
shouldIncludeInMessages: true, shouldIncludeInMessages: true,
showExplanationBubble: false, showExplanationBubble: false,
notebookServerInfo: { notebookServerInfo: {
notebookServerEndpoint: undefined, notebookServerEndpoint: undefined,
authToken: undefined, authToken: undefined,
forwardingId: undefined, forwardingId: undefined,
}, } as DataModels.NotebookWorkspaceConnectionInfo,
containerStatus: { containerStatus: {
status: undefined, status: undefined,
durationLeftInMinutes: undefined, durationLeftInMinutes: undefined,
phoenixServerInfo: undefined, phoenixServerInfo: undefined,
}, } as DataModels.ContainerInfo,
isAllocatingContainer: false, isAllocatingContainer: false,
})); }));
}, },
@ -137,3 +139,4 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
}; };
export { CopilotProvider, useCopilotStore }; export { CopilotProvider, useCopilotStore };

View File

@ -77,39 +77,39 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
this.subscriptions.push( this.subscriptions.push(
{ {
dispose: useNotebook.subscribe( dispose: useNotebook.subscribe(
() => this.setState({}),
(state) => state.isNotebookEnabled, (state) => state.isNotebookEnabled,
() => this.setState({}),
), ),
}, },
{ dispose: useSelectedNode.subscribe(() => this.setState({})) }, { dispose: useSelectedNode.subscribe(() => this.setState({})) },
{ {
dispose: useCarousel.subscribe( dispose: useCarousel.subscribe(
() => this.setState({}),
(state) => state.showCoachMark, (state) => state.showCoachMark,
() => this.setState({}),
), ),
}, },
{ {
dispose: usePostgres.subscribe( dispose: usePostgres.subscribe(
() => this.setState({}),
(state) => state.showPostgreTeachingBubble, (state) => state.showPostgreTeachingBubble,
() => this.setState({}),
), ),
}, },
{ {
dispose: usePostgres.subscribe( dispose: usePostgres.subscribe(
() => this.setState({}),
(state) => state.showResetPasswordBubble, (state) => state.showResetPasswordBubble,
() => this.setState({}),
), ),
}, },
{ {
dispose: useDatabases.subscribe( dispose: useDatabases.subscribe(
() => this.setState({}),
(state) => state.sampleDataResourceTokenCollection, (state) => state.sampleDataResourceTokenCollection,
() => this.setState({}),
), ),
}, },
{ {
dispose: useQueryCopilot.subscribe( dispose: useQueryCopilot.subscribe(
() => this.setState({}),
(state) => state.copilotEnabled, (state) => state.copilotEnabled,
() => this.setState({}),
), ),
}, },
); );

View File

@ -1,7 +1,7 @@
import { FitAddon } from "@xterm/addon-fit"; import { FitAddon } from "@xterm/addon-fit";
import { Terminal } from "@xterm/xterm"; import { Terminal } from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import "xterm/css/xterm.css";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { TerminalKind } from "../../../Contracts/ViewModels"; import { TerminalKind } from "../../../Contracts/ViewModels";
import { startCloudShellTerminal } from "./CloudShellTerminalCore"; import { startCloudShellTerminal } from "./CloudShellTerminalCore";

View File

@ -44,8 +44,8 @@ export default class NotebookTabV2 extends NotebookTabBase {
this.container = options.container; this.container = options.container;
this.notebookPath = ko.observable(options.notebookContentItem.path); this.notebookPath = ko.observable(options.notebookContentItem.path);
useNotebook.subscribe( useNotebook.subscribe(
() => logConsoleInfo("New notebook server info received."),
(state) => state.notebookServerInfo, (state) => state.notebookServerInfo,
() => logConsoleInfo("New notebook server info received."),
); );
this.notebookComponentAdapter = new NotebookComponentAdapter({ this.notebookComponentAdapter = new NotebookComponentAdapter({
contentItem: options.notebookContentItem, contentItem: options.notebookContentItem,
@ -165,7 +165,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
{ {
iconSrc: null, iconSrc: null,
iconAlt: kernelLabel, iconAlt: kernelLabel,
onCommandClick: () => {}, onCommandClick: () => { },
commandButtonLabel: null, commandButtonLabel: null,
hasPopup: false, hasPopup: false,
disabled: availableKernels.length < 1, disabled: availableKernels.length < 1,
@ -276,7 +276,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
{ {
iconSrc: null, iconSrc: null,
iconAlt: null, iconAlt: null,
onCommandClick: () => {}, onCommandClick: () => { },
commandButtonLabel: null, commandButtonLabel: null,
ariaLabel: cellTypeLabel, ariaLabel: cellTypeLabel,
hasPopup: false, hasPopup: false,

View File

@ -16,7 +16,6 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react"; import * as React from "react";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import shallow from "zustand/shallow";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
@ -38,13 +37,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
const [openItems, setOpenItems] = React.useState<TreeItemValue[]>([]); const [openItems, setOpenItems] = React.useState<TreeItemValue[]>([]);
const treeStyles = useTreeStyles(); const treeStyles = useTreeStyles();
const { isNotebookEnabled } = useNotebook( const isNotebookEnabled = useNotebook((state) => state.isNotebookEnabled)
(state) => ({
isNotebookEnabled: state.isNotebookEnabled,
}),
shallow,
);
// We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes. // We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes.
const { refreshActiveTab } = useTabs(); const { refreshActiveTab } = useTabs();

View File

@ -14,7 +14,6 @@ import PublishIcon from "../../../images/notebook/publish_content.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg"; import RefreshIcon from "../../../images/refresh-cosmos.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { IPinnedRepo } from "../../Juno/JunoClient"; import { IPinnedRepo } from "../../Juno/JunoClient";
@ -58,12 +57,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
useSelectedNode.subscribe(() => this.triggerRender()); useSelectedNode.subscribe(() => this.triggerRender());
useTabs.subscribe( useTabs.subscribe(
() => this.triggerRender(),
(state) => state.activeTab, (state) => state.activeTab,
() => this.triggerRender(),
); );
useNotebook.subscribe( useNotebook.subscribe(
() => this.triggerRender(),
(state) => state.isNotebookEnabled, (state) => state.isNotebookEnabled,
() => this.triggerRender(),
); );
useDatabases.subscribe(() => this.triggerRender()); useDatabases.subscribe(() => this.triggerRender());

View File

@ -1,5 +1,6 @@
import _ from "underscore"; import _ from "underscore";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
@ -26,143 +27,147 @@ interface DatabasesState {
validateCollectionId: (databaseId: string, collectionId: string) => Promise<boolean>; validateCollectionId: (databaseId: string, collectionId: string) => Promise<boolean>;
} }
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({ export const useDatabases = create<DatabasesState>()(
databases: [], subscribeWithSelector(
resourceTokenCollection: undefined, (set, get) => ({
sampleDataResourceTokenCollection: undefined, databases: [] as ViewModels.Database[],
updateDatabase: (updatedDatabase: ViewModels.Database) => resourceTokenCollection: undefined as ViewModels.CollectionBase,
set((state) => { sampleDataResourceTokenCollection: undefined as ViewModels.CollectionBase,
const updatedDatabases = state.databases.map((database: ViewModels.Database) => { updateDatabase: (updatedDatabase: ViewModels.Database) =>
if (database?.id() === updatedDatabase?.id()) { set((state) => {
return updatedDatabase; const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
if (database?.id() === updatedDatabase?.id()) {
return updatedDatabase;
}
return database;
});
return { databases: updatedDatabases };
}),
addDatabases: (databases: ViewModels.Database[]) =>
set((state) => ({
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())),
})),
deleteDatabase: (database: ViewModels.Database) =>
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
clearDatabases: () => set(() => ({ databases: [] })),
isSaveQueryEnabled: () => {
const savedQueriesDatabase: ViewModels.Database = _.find(
get().databases,
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName,
);
if (!savedQueriesDatabase) {
return false;
}
const savedQueriesCollection: ViewModels.Collection =
savedQueriesDatabase &&
_.find(
savedQueriesDatabase.collections(),
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName,
);
if (!savedQueriesCollection) {
return false;
}
return true;
},
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
return isSampleDatabase === undefined
? get().databases.find((db) => databaseId === db.id())
: get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
},
isLastNonEmptyDatabase: () => {
const databases = get().databases;
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
},
findCollection: (databaseId: string, collectionId: string) => {
const database = get().findDatabaseWithId(databaseId);
return database?.collections()?.find((collection) => collection.id() === collectionId);
},
isLastCollection: () => {
const databases = get().databases;
if (databases.length === 0) {
return false;
} }
return database; let collectionCount = 0;
}); for (let i = 0; i < databases.length; i++) {
return { databases: updatedDatabases }; const database = databases[i];
}), collectionCount += database.collections().length;
addDatabases: (databases: ViewModels.Database[]) => if (collectionCount > 1) {
set((state) => ({ return false;
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())), }
})), }
deleteDatabase: (database: ViewModels.Database) =>
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
clearDatabases: () => set(() => ({ databases: [] })),
isSaveQueryEnabled: () => {
const savedQueriesDatabase: ViewModels.Database = _.find(
get().databases,
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName,
);
if (!savedQueriesDatabase) {
return false;
}
const savedQueriesCollection: ViewModels.Collection =
savedQueriesDatabase &&
_.find(
savedQueriesDatabase.collections(),
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName,
);
if (!savedQueriesCollection) {
return false;
}
return true;
},
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
return isSampleDatabase === undefined
? get().databases.find((db) => databaseId === db.id())
: get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
},
isLastNonEmptyDatabase: () => {
const databases = get().databases;
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
},
findCollection: (databaseId: string, collectionId: string) => {
const database = get().findDatabaseWithId(databaseId);
return database?.collections()?.find((collection) => collection.id() === collectionId);
},
isLastCollection: () => {
const databases = get().databases;
if (databases.length === 0) {
return false;
}
let collectionCount = 0; return true;
for (let i = 0; i < databases.length; i++) { },
const database = databases[i]; loadDatabaseOffers: async () => {
collectionCount += database.collections().length;
if (collectionCount > 1) {
return false;
}
}
return true;
},
loadDatabaseOffers: async () => {
await Promise.all(
get().databases?.map(async (database: ViewModels.Database) => {
await database.loadOffer();
}),
);
},
loadAllOffers: async () => {
await Promise.all(
get().databases?.map(async (database: ViewModels.Database) => {
await database.loadOffer();
await database.loadCollections();
await Promise.all( await Promise.all(
(database.collections() || []).map(async (collection: ViewModels.Collection) => { get().databases?.map(async (database: ViewModels.Database) => {
await collection.loadOffer(); await database.loadOffer();
}), }),
); );
}), },
); loadAllOffers: async () => {
}, await Promise.all(
isFirstResourceCreated: () => { get().databases?.map(async (database: ViewModels.Database) => {
const databases = get().databases; await database.loadOffer();
await database.loadCollections();
await Promise.all(
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
await collection.loadOffer();
}),
);
}),
);
},
isFirstResourceCreated: () => {
const databases = get().databases;
if (!databases || databases.length === 0) { if (!databases || databases.length === 0) {
return false; return false;
} }
return databases.some((database) => { return databases.some((database) => {
// user has created at least one collection // user has created at least one collection
if (database.collections()?.length > 0) { if (database.collections()?.length > 0) {
return true; return true;
} }
// user has created a database with shared throughput // user has created a database with shared throughput
if (database.offer()) { if (database.offer()) {
return true; return true;
} }
// use has created an empty database without shared throughput // use has created an empty database without shared throughput
return false; return false;
}); });
}, },
findSelectedDatabase: (): ViewModels.Database => { findSelectedDatabase: (): ViewModels.Database => {
const selectedNode = useSelectedNode.getState().selectedNode; const selectedNode = useSelectedNode.getState().selectedNode;
if (!selectedNode) { if (!selectedNode) {
return undefined; return undefined;
} }
if (selectedNode.nodeKind === "Database") { if (selectedNode.nodeKind === "Database") {
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id()); return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
} }
if (selectedNode.nodeKind === "Collection") { if (selectedNode.nodeKind === "Collection") {
return selectedNode.database; return selectedNode.database;
} }
return selectedNode.collection?.database; return selectedNode.collection?.database;
}, },
validateDatabaseId: (id: string): boolean => { validateDatabaseId: (id: string): boolean => {
return !get().databases.some((database) => database.id() === id); return !get().databases.some((database) => database.id() === id);
}, },
validateCollectionId: async (databaseId: string, collectionId: string): Promise<boolean> => { validateCollectionId: async (databaseId: string, collectionId: string): Promise<boolean> => {
const database = get().databases.find((db) => db.id() === databaseId); const database = get().databases.find((db) => db.id() === databaseId);
// For a new tables account, database is undefined when creating the first table // For a new tables account, database is undefined when creating the first table
if (!database && userContext.apiType === "Tables") { if (!database && userContext.apiType === "Tables") {
return true; return true;
} }
await database.loadCollections(); await database.loadCollections();
return !database.collections().some((collection) => collection.id() === collectionId); return !database.collections().some((collection) => collection.id() === collectionId);
}, },
})); })
)
);

View File

@ -1,6 +1,6 @@
import { ConnectionStatusType, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants"; import { ConnectionStatusType, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { useTabs } from "../hooks/useTabs"; import { useTabs } from "../hooks/useTabs";
export interface SelectedNodeState { export interface SelectedNodeState {
@ -17,7 +17,7 @@ export interface SelectedNodeState {
isQueryCopilotCollectionSelected: () => boolean; isQueryCopilotCollectionSelected: () => boolean;
} }
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({ export const useSelectedNode = create<SelectedNodeState>((set, get) => ({
selectedNode: undefined, selectedNode: undefined,
setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }), setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }),
isDatabaseNodeOrNoneSelected: (): boolean => { isDatabaseNodeOrNoneSelected: (): boolean => {

View File

@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { PropsWithChildren, useEffect } from "react"; import { PropsWithChildren, useEffect } from "react";
import { KeyBindingMap, tinykeys } from "tinykeys"; import { KeyBindingMap, tinykeys } from "tinykeys";
import create, { UseStore } from "zustand"; import { create } from "zustand";
/** /**
* Represents a keyboard shortcut handler. * Represents a keyboard shortcut handler.
@ -126,7 +126,7 @@ export const clearKeyboardActionGroup = (group: KeyboardActionGroup) => {
useKeyboardActionHandlers.getState().setHandlers(group, {}); useKeyboardActionHandlers.getState().setHandlers(group, {});
}; };
const useKeyboardActionHandlers: UseStore<KeyboardShortcutState> = create((set, get) => ({ const useKeyboardActionHandlers = create<KeyboardShortcutState>((set, get) => ({
allHandlers: {}, allHandlers: {},
groups: {}, groups: {},
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => { setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {

View File

@ -1,4 +1,5 @@
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
interface CarouselState { interface CarouselState {
shouldOpen: boolean; shouldOpen: boolean;
@ -9,11 +10,15 @@ interface CarouselState {
setShowCopilotCarousel: (showCopilotCarousel: boolean) => void; setShowCopilotCarousel: (showCopilotCarousel: boolean) => void;
} }
export const useCarousel: UseStore<CarouselState> = create((set) => ({ export const useCarousel = create<CarouselState>()(
shouldOpen: false, subscribeWithSelector(
showCoachMark: false, (set) => ({
showCopilotCarousel: false, shouldOpen: false,
setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }), showCoachMark: false,
setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }), showCopilotCarousel: false,
setShowCopilotCarousel: (showCopilotCarousel: boolean) => set({ showCopilotCarousel }), setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }),
})); setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }),
setShowCopilotCarousel: (showCopilotCarousel: boolean) => set({ showCopilotCarousel }),
})
)
);

View File

@ -1,10 +1,10 @@
import create, { UseStore } from "zustand"; import { create } from "zustand";
interface ClientWriteEnabledState { interface ClientWriteEnabledState {
clientWriteEnabled: boolean; clientWriteEnabled: boolean;
setClientWriteEnabled: (writeEnabled: boolean) => void; setClientWriteEnabled: (writeEnabled: boolean) => void;
} }
export const useClientWriteEnabled: UseStore<ClientWriteEnabledState> = create((set) => ({ export const useClientWriteEnabled = create<ClientWriteEnabledState>((set) => ({
clientWriteEnabled: true, clientWriteEnabled: true,
setClientWriteEnabled: (clientWriteEnabled: boolean) => set({ clientWriteEnabled }), setClientWriteEnabled: (clientWriteEnabled: boolean) => set({ clientWriteEnabled }),
})); }));

View File

@ -1,6 +1,6 @@
import { getDataTransferJobs } from "Common/dataAccess/dataTransfers"; import { getDataTransferJobs } from "Common/dataAccess/dataTransfers";
import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types"; import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
import create, { UseStore } from "zustand"; import { create } from "zustand";
export interface DataTransferJobsState { export interface DataTransferJobsState {
dataTransferJobs: DataTransferJobGetResults[]; dataTransferJobs: DataTransferJobGetResults[];
@ -9,9 +9,7 @@ export interface DataTransferJobsState {
setPollingDataTransferJobs: (pollingDataTransferJobs: Set<string>) => void; setPollingDataTransferJobs: (pollingDataTransferJobs: Set<string>) => void;
} }
type DataTransferJobStore = UseStore<DataTransferJobsState>; export const useDataTransferJobs = create<DataTransferJobsState>((set) => ({
export const useDataTransferJobs: DataTransferJobStore = create((set) => ({
dataTransferJobs: [], dataTransferJobs: [],
pollingDataTransferJobs: new Set<string>(), pollingDataTransferJobs: new Set<string>(),
setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }), setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }),

View File

@ -1,4 +1,4 @@
import create, { UseStore } from "zustand"; import { create } from "zustand";
export interface NotebookSnapshotHooks { export interface NotebookSnapshotHooks {
snapshot?: string; snapshot?: string;
@ -7,7 +7,7 @@ export interface NotebookSnapshotHooks {
setError: (error: string) => void; setError: (error: string) => void;
} }
export const useNotebookSnapshotStore: UseStore<NotebookSnapshotHooks> = create((set) => ({ export const useNotebookSnapshotStore = create<NotebookSnapshotHooks>((set) => ({
snapshot: undefined, snapshot: undefined,
error: undefined, error: undefined,
setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })), setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })),

View File

@ -1,4 +1,4 @@
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
export interface NotificationConsoleState { export interface NotificationConsoleState {
@ -15,7 +15,7 @@ export interface NotificationConsoleState {
setConsoleAnimationFinished: (consoleAnimationFinished: boolean) => void; setConsoleAnimationFinished: (consoleAnimationFinished: boolean) => void;
} }
export const useNotificationConsole: UseStore<NotificationConsoleState> = create((set) => ({ export const useNotificationConsole = create<NotificationConsoleState>((set) => ({
isExpanded: false, isExpanded: false,
consoleData: undefined, consoleData: undefined,
inProgressConsoleDataIdToBeDeleted: "", inProgressConsoleDataIdToBeDeleted: "",

View File

@ -1,4 +1,5 @@
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
interface TeachingBubbleState { interface TeachingBubbleState {
showPostgreTeachingBubble: boolean; showPostgreTeachingBubble: boolean;
@ -7,9 +8,13 @@ interface TeachingBubbleState {
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void; setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void;
} }
export const usePostgres: UseStore<TeachingBubbleState> = create((set) => ({ export const usePostgres = create<TeachingBubbleState>()(
showPostgreTeachingBubble: false, subscribeWithSelector(
showResetPasswordBubble: false, (set) => ({
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }), showPostgreTeachingBubble: false,
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }), showResetPasswordBubble: false,
})); setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }),
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }),
})
)
);

View File

@ -4,7 +4,8 @@ import { QueryResults } from "Contracts/ViewModels";
import { CopilotMessage, CopilotSchemaAllocationInfo } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; import { CopilotMessage, CopilotSchemaAllocationInfo } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { guid } from "Explorer/Tables/Utilities"; import { guid } from "Explorer/Tables/Utilities";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { ContainerInfo } from "../Contracts/DataModels"; import { ContainerInfo } from "../Contracts/DataModels";
@ -96,120 +97,12 @@ export interface QueryCopilotState {
resetQueryCopilotStates: () => void; resetQueryCopilotStates: () => void;
} }
type QueryCopilotStore = UseStore<Partial<QueryCopilotState>>; export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
subscribeWithSelector(
export const useQueryCopilot: QueryCopilotStore = create((set) => ({ (set) => ({
copilotEnabled: false, copilotEnabled: false,
copilotUserDBEnabled: false, copilotUserDBEnabled: false,
copilotSampleDBEnabled: false, copilotSampleDBEnabled: false,
generatedQuery: "",
likeQuery: false,
userPrompt: "",
showFeedbackModal: false,
hideFeedbackModalForLikedQueries: false,
correlationId: "",
query: "SELECT * FROM c",
selectedQuery: "",
isGeneratingQuery: null,
isGeneratingExplanation: false,
isExecuting: false,
dislikeQuery: undefined,
showCallout: false,
showSamplePrompts: false,
queryIterator: undefined,
queryResults: undefined,
errors: [],
isSamplePromptsOpen: false,
showDeletePopup: false,
showFeedbackBar: false,
showCopyPopup: false,
showErrorMessageBar: false,
showInvalidQueryMessageBar: false,
generatedQueryComments: "",
wasCopilotUsed: false,
showWelcomeSidebar: true,
showCopilotSidebar: false,
chatMessages: [],
shouldIncludeInMessages: true,
showExplanationBubble: false,
notebookServerInfo: {
notebookServerEndpoint: undefined,
authToken: undefined,
forwardingId: undefined,
},
containerStatus: {
status: undefined,
durationLeftInMinutes: undefined,
phoenixServerInfo: undefined,
},
schemaAllocationInfo: {
databaseId: undefined,
containerId: undefined,
},
isAllocatingContainer: false,
copilotEnabledforExecution: false,
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
closeFeedbackModal: () => set({ showFeedbackModal: false }),
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
set({ hideFeedbackModalForLikedQueries }),
refreshCorrelationId: () => set({ correlationId: guid() }),
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
setQuery: (query: string) => set({ query }),
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
setShowCallout: (showCallout: boolean) => set({ showCallout }),
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
setErrors: (errors: QueryError[]) => set({ errors }),
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
set({ notebookServerInfo }),
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
setIsAllocatingContainer: (isAllocatingContainer: boolean) => set({ isAllocatingContainer }),
setSchemaAllocationInfo: (schemaAllocationInfo: CopilotSchemaAllocationInfo) => set({ schemaAllocationInfo }),
setCopilotEnabledforExecution: (copilotEnabledforExecution: boolean) => set({ copilotEnabledforExecution }),
resetContainerConnection: (): void => {
useTabs.getState().closeAllNotebookTabs(true);
useQueryCopilot.getState().setNotebookServerInfo(undefined);
useQueryCopilot.getState().setIsAllocatingContainer(false);
useQueryCopilot.getState().setContainerStatus({
status: undefined,
durationLeftInMinutes: undefined,
phoenixServerInfo: undefined,
});
useQueryCopilot.getState().setSchemaAllocationInfo({
databaseId: undefined,
containerId: undefined,
});
},
resetQueryCopilotStates: () => {
set((state) => ({
...state,
generatedQuery: "", generatedQuery: "",
likeQuery: false, likeQuery: false,
userPrompt: "", userPrompt: "",
@ -218,15 +111,15 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
correlationId: "", correlationId: "",
query: "SELECT * FROM c", query: "SELECT * FROM c",
selectedQuery: "", selectedQuery: "",
isGeneratingQuery: false, isGeneratingQuery: null as boolean,
isGeneratingExplanation: false, isGeneratingExplanation: false,
isExecuting: false, isExecuting: false,
dislikeQuery: undefined, dislikeQuery: undefined as (boolean | undefined),
showCallout: false, showCallout: false,
showSamplePrompts: false, showSamplePrompts: false,
queryIterator: undefined, queryIterator: undefined as MinimalQueryIterator | undefined,
queryResults: undefined, queryResults: undefined as QueryResults | undefined,
errors: [], errors: [] as QueryError[],
isSamplePromptsOpen: false, isSamplePromptsOpen: false,
showDeletePopup: false, showDeletePopup: false,
showFeedbackBar: false, showFeedbackBar: false,
@ -235,25 +128,135 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
showInvalidQueryMessageBar: false, showInvalidQueryMessageBar: false,
generatedQueryComments: "", generatedQueryComments: "",
wasCopilotUsed: false, wasCopilotUsed: false,
showWelcomeSidebar: true,
showCopilotSidebar: false, showCopilotSidebar: false,
chatMessages: [], chatMessages: [] as CopilotMessage[],
shouldIncludeInMessages: true, shouldIncludeInMessages: true,
showExplanationBubble: false, showExplanationBubble: false,
notebookServerInfo: { notebookServerInfo: {
notebookServerEndpoint: undefined, notebookServerEndpoint: undefined,
authToken: undefined, authToken: undefined,
forwardingId: undefined, forwardingId: undefined,
}, } as DataModels.NotebookWorkspaceConnectionInfo,
containerStatus: { containerStatus: {
status: undefined, status: undefined,
durationLeftInMinutes: undefined, durationLeftInMinutes: undefined,
phoenixServerInfo: undefined, phoenixServerInfo: undefined,
}, } as ContainerInfo,
schemaAllocationInfo: { schemaAllocationInfo: {
databaseId: undefined, databaseId: undefined,
containerId: undefined, containerId: undefined,
}, } as CopilotSchemaAllocationInfo,
isAllocatingContainer: false, isAllocatingContainer: false,
})); copilotEnabledforExecution: false,
},
})); setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
closeFeedbackModal: () => set({ showFeedbackModal: false }),
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
set({ hideFeedbackModalForLikedQueries }),
refreshCorrelationId: () => set({ correlationId: guid() }),
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
setQuery: (query: string) => set({ query }),
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
setShowCallout: (showCallout: boolean) => set({ showCallout }),
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
setErrors: (errors: QueryError[]) => set({ errors }),
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
set({ notebookServerInfo }),
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
setIsAllocatingContainer: (isAllocatingContainer: boolean) => set({ isAllocatingContainer }),
setSchemaAllocationInfo: (schemaAllocationInfo: CopilotSchemaAllocationInfo) => set({ schemaAllocationInfo }),
setCopilotEnabledforExecution: (copilotEnabledforExecution: boolean) => set({ copilotEnabledforExecution }),
resetContainerConnection: (): void => {
useTabs.getState().closeAllNotebookTabs(true);
useQueryCopilot.getState().setNotebookServerInfo(undefined);
useQueryCopilot.getState().setIsAllocatingContainer(false);
useQueryCopilot.getState().setContainerStatus({
status: undefined,
durationLeftInMinutes: undefined,
phoenixServerInfo: undefined,
});
useQueryCopilot.getState().setSchemaAllocationInfo({
databaseId: undefined,
containerId: undefined,
});
},
resetQueryCopilotStates: () => {
set((state) => ({
...state,
generatedQuery: "",
likeQuery: false,
userPrompt: "",
showFeedbackModal: false,
hideFeedbackModalForLikedQueries: false,
correlationId: "",
query: "SELECT * FROM c",
selectedQuery: "",
isGeneratingQuery: false,
isGeneratingExplanation: false,
isExecuting: false,
dislikeQuery: undefined,
showCallout: false,
showSamplePrompts: false,
queryIterator: undefined,
queryResults: undefined,
errors: [],
isSamplePromptsOpen: false,
showDeletePopup: false,
showFeedbackBar: false,
showCopyPopup: false,
showErrorMessageBar: false,
showInvalidQueryMessageBar: false,
generatedQueryComments: "",
wasCopilotUsed: false,
showCopilotSidebar: false,
chatMessages: [],
shouldIncludeInMessages: true,
showExplanationBubble: false,
notebookServerInfo: {
notebookServerEndpoint: undefined,
authToken: undefined,
forwardingId: undefined,
},
containerStatus: {
status: undefined,
durationLeftInMinutes: undefined,
phoenixServerInfo: undefined,
},
schemaAllocationInfo: {
databaseId: undefined,
containerId: undefined,
},
isAllocatingContainer: false,
}));
},
})
)
);

View File

@ -1,4 +1,4 @@
import create, { UseStore } from "zustand"; import { create } from "zustand";
export interface SidePanelState { export interface SidePanelState {
isOpen: boolean; isOpen: boolean;
@ -9,7 +9,7 @@ export interface SidePanelState {
closeSidePanel: () => void; closeSidePanel: () => void;
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element. getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
} }
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({ export const useSidePanel = create<SidePanelState>((set) => ({
isOpen: false, isOpen: false,
panelWidth: "440px", panelWidth: "440px",
openSidePanel: (headerText, panelContent, panelWidth = "440px") => openSidePanel: (headerText, panelContent, panelWidth = "440px") =>

View File

@ -7,7 +7,8 @@ import {
OPEN_TABS_SUBCOMPONENT_NAME, OPEN_TABS_SUBCOMPONENT_NAME,
saveSubComponentState, saveSubComponentState,
} from "Shared/AppStatePersistenceUtility"; } from "Shared/AppStatePersistenceUtility";
import create, { UseStore } from "zustand"; import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { CollectionTabKind } from "../Contracts/ViewModels"; import { CollectionTabKind } from "../Contracts/ViewModels";
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab"; import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
@ -51,194 +52,198 @@ export enum ReactTabKind {
QueryCopilot, QueryCopilot,
} }
export const useTabs: UseStore<TabsState> = create((set, get) => ({ export const useTabs = create<TabsState>()(
openedTabs: [] as TabsBase[], subscribeWithSelector(
openedReactTabs: [ReactTabKind.Home], (set, get) => ({
activeTab: undefined as TabsBase, openedTabs: [] as TabsBase[],
activeReactTab: ReactTabKind.Home, openedReactTabs: [ReactTabKind.Home],
queryCopilotTabInitialInput: "", activeTab: undefined as TabsBase,
isTabExecuting: false, activeReactTab: ReactTabKind.Home,
isQueryErrorThrown: false, queryCopilotTabInitialInput: "",
activateTab: (tab: TabsBase): void => { isTabExecuting: false,
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) { isQueryErrorThrown: false,
set({ activeTab: tab, activeReactTab: undefined }); activateTab: (tab: TabsBase): void => {
tab.onActivate(); if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
} set({ activeTab: tab, activeReactTab: undefined });
}, tab.onActivate();
activateNewTab: (tab: TabsBase): void => {
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
tab.triggerPersistState = get().persistTabsState;
tab.onActivate();
get().persistTabsState();
},
activateReactTab: (tabKind: ReactTabKind): void => {
// Clear the selected node when switching to a react tab.
useSelectedNode.getState().setSelectedNode(undefined);
set({ activeTab: undefined, activeReactTab: tabKind });
},
updateTab: (tab: TabsBase) => {
if (get().activeTab?.tabId === tab.tabId) {
set({ activeTab: tab });
}
set((state) => ({
openedTabs: state.openedTabs.map((openedTab) => {
if (openedTab.tabId === tab.tabId) {
return tab;
} }
return openedTab; },
}), activateNewTab: (tab: TabsBase): void => {
})); set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
}, tab.triggerPersistState = get().persistTabsState;
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] => tab.onActivate();
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))), get().persistTabsState();
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => { },
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state activateReactTab: (tabKind: ReactTabKind): void => {
const activeTab = get().activeTab; // Clear the selected node when switching to a react tab.
activeTab && comparator(activeTab) && activeTab.onActivate(); useSelectedNode.getState().setSelectedNode(undefined);
}, set({ activeTab: undefined, activeReactTab: tabKind });
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void => },
get() updateTab: (tab: TabsBase) => {
.openedTabs.filter(comparator) if (get().activeTab?.tabId === tab.tabId) {
.forEach((tab) => tab.onCloseTabButtonClick()), set({ activeTab: tab });
closeTab: (tab: TabsBase): void => {
let tabIndex: number;
const { activeTab, openedTabs, openedReactTabs } = get();
const updatedTabs = openedTabs.filter((openedTab, index) => {
if (tab.tabId === openedTab.tabId) {
tabIndex = index;
return false;
}
return true;
});
if (updatedTabs.length === 0 && !isFabricMirrored()) {
set({ activeTab: undefined, activeReactTab: undefined });
}
if (tab.tabId === activeTab?.tabId && tabIndex !== -1) {
const tabToTheRight = updatedTabs[tabIndex];
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
const newActiveTab = tabToTheRight ?? lastOpenTab;
set({ activeTab: newActiveTab });
if (newActiveTab) {
newActiveTab.onActivate();
}
}
set({ openedTabs: updatedTabs });
if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
}
get().persistTabsState();
},
closeAllNotebookTabs: (hardClose): void => {
const isNotebook = (tabKind: CollectionTabKind): boolean => {
if (
tabKind === CollectionTabKind.Notebook ||
tabKind === CollectionTabKind.NotebookV2 ||
tabKind === CollectionTabKind.SchemaAnalyzer ||
tabKind === CollectionTabKind.Terminal
) {
return true;
}
return false;
};
const tabList = get().openedTabs;
if (tabList && tabList.length > 0) {
tabList.forEach((tab: NotebookTabV2) => {
const tabKind: CollectionTabKind = tab.tabKind;
if (tabKind && isNotebook(tabKind)) {
tab.onCloseTabButtonClick(hardClose);
} }
});
if (get().openedTabs.length === 0 && !isFabricMirrored()) { set((state) => ({
set({ activeTab: undefined, activeReactTab: undefined }); openedTabs: state.openedTabs.map((openedTab) => {
} if (openedTab.tabId === tab.tabId) {
} return tab;
}, }
openAndActivateReactTab: (tabKind: ReactTabKind) => { return openedTab;
if (get().openedReactTabs.indexOf(tabKind) === -1) { }),
set((state) => ({ }));
openedReactTabs: [...state.openedReactTabs, tabKind], },
})); getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
} get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
const activeTab = get().activeTab;
activeTab && comparator(activeTab) && activeTab.onActivate();
},
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
get()
.openedTabs.filter(comparator)
.forEach((tab) => tab.onCloseTabButtonClick()),
closeTab: (tab: TabsBase): void => {
let tabIndex: number;
const { activeTab, openedTabs, openedReactTabs } = get();
const updatedTabs = openedTabs.filter((openedTab, index) => {
if (tab.tabId === openedTab.tabId) {
tabIndex = index;
return false;
}
return true;
});
if (updatedTabs.length === 0 && !isFabricMirrored()) {
set({ activeTab: undefined, activeReactTab: undefined });
}
set({ activeTab: undefined, activeReactTab: tabKind }); if (tab.tabId === activeTab?.tabId && tabIndex !== -1) {
}, const tabToTheRight = updatedTabs[tabIndex];
closeReactTab: (tabKind: ReactTabKind) => { const lastOpenTab = updatedTabs[updatedTabs.length - 1];
const { activeReactTab, openedTabs, openedReactTabs } = get(); const newActiveTab = tabToTheRight ?? lastOpenTab;
const updatedOpenedReactTabs = openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab); set({ activeTab: newActiveTab });
if (activeReactTab === tabKind) { if (newActiveTab) {
openedTabs?.length > 0 newActiveTab.onActivate();
? set({ activeTab: openedTabs[0], activeReactTab: undefined }) }
: set({ activeTab: undefined, activeReactTab: updatedOpenedReactTabs[0] }); }
}
set({ openedReactTabs: updatedOpenedReactTabs }); set({ openedTabs: updatedTabs });
},
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
setIsTabExecuting: (state: boolean) => {
set({ isTabExecuting: state });
},
setIsQueryErrorThrown: (state: boolean) => {
set({ isQueryErrorThrown: state });
},
getCurrentTabIndex: () => {
const state = get();
if (state.activeReactTab !== undefined) {
return state.openedReactTabs.indexOf(state.activeReactTab);
} else if (state.activeTab !== undefined) {
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
if (nonReactTabIndex !== -1) {
return state.openedReactTabs.length + nonReactTabIndex;
}
}
return -1; if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
}, set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
selectTabByIndex: (index: number) => { }
const state = get();
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
const clampedIndex = clamp(index, totalTabCount - 1, 0);
if (clampedIndex < state.openedReactTabs.length) { get().persistTabsState();
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] }); },
} else { closeAllNotebookTabs: (hardClose): void => {
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined }); const isNotebook = (tabKind: CollectionTabKind): boolean => {
} if (
}, tabKind === CollectionTabKind.Notebook ||
selectLeftTab: () => { tabKind === CollectionTabKind.NotebookV2 ||
const state = get(); tabKind === CollectionTabKind.SchemaAnalyzer ||
state.selectTabByIndex(state.getCurrentTabIndex() - 1); tabKind === CollectionTabKind.Terminal
}, ) {
selectRightTab: () => { return true;
const state = get(); }
state.selectTabByIndex(state.getCurrentTabIndex() + 1); return false;
}, };
closeActiveTab: () => {
const state = get();
if (state.activeReactTab !== undefined) {
state.closeReactTab(state.activeReactTab);
} else if (state.activeTab !== undefined) {
state.closeTab(state.activeTab);
}
},
closeAllTabs: () => {
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
},
persistTabsState: () => {
const state = get();
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
saveSubComponentState<OpenTab[]>( const tabList = get().openedTabs;
AppStateComponentNames.DataExplorerAction, if (tabList && tabList.length > 0) {
OPEN_TABS_SUBCOMPONENT_NAME, tabList.forEach((tab: NotebookTabV2) => {
undefined, const tabKind: CollectionTabKind = tab.tabKind;
openTabsStates, if (tabKind && isNotebook(tabKind)) {
); tab.onCloseTabButtonClick(hardClose);
}, }
})); });
if (get().openedTabs.length === 0 && !isFabricMirrored()) {
set({ activeTab: undefined, activeReactTab: undefined });
}
}
},
openAndActivateReactTab: (tabKind: ReactTabKind) => {
if (get().openedReactTabs.indexOf(tabKind) === -1) {
set((state) => ({
openedReactTabs: [...state.openedReactTabs, tabKind],
}));
}
set({ activeTab: undefined, activeReactTab: tabKind });
},
closeReactTab: (tabKind: ReactTabKind) => {
const { activeReactTab, openedTabs, openedReactTabs } = get();
const updatedOpenedReactTabs = openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab);
if (activeReactTab === tabKind) {
openedTabs?.length > 0
? set({ activeTab: openedTabs[0], activeReactTab: undefined })
: set({ activeTab: undefined, activeReactTab: updatedOpenedReactTabs[0] });
}
set({ openedReactTabs: updatedOpenedReactTabs });
},
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
setIsTabExecuting: (state: boolean) => {
set({ isTabExecuting: state });
},
setIsQueryErrorThrown: (state: boolean) => {
set({ isQueryErrorThrown: state });
},
getCurrentTabIndex: () => {
const state = get();
if (state.activeReactTab !== undefined) {
return state.openedReactTabs.indexOf(state.activeReactTab);
} else if (state.activeTab !== undefined) {
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
if (nonReactTabIndex !== -1) {
return state.openedReactTabs.length + nonReactTabIndex;
}
}
return -1;
},
selectTabByIndex: (index: number) => {
const state = get();
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
const clampedIndex = clamp(index, totalTabCount - 1, 0);
if (clampedIndex < state.openedReactTabs.length) {
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
} else {
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
}
},
selectLeftTab: () => {
const state = get();
state.selectTabByIndex(state.getCurrentTabIndex() - 1);
},
selectRightTab: () => {
const state = get();
state.selectTabByIndex(state.getCurrentTabIndex() + 1);
},
closeActiveTab: () => {
const state = get();
if (state.activeReactTab !== undefined) {
state.closeReactTab(state.activeReactTab);
} else if (state.activeTab !== undefined) {
state.closeTab(state.activeTab);
}
},
closeAllTabs: () => {
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
},
persistTabsState: () => {
const state = get();
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
saveSubComponentState<OpenTab[]>(
AppStateComponentNames.DataExplorerAction,
OPEN_TABS_SUBCOMPONENT_NAME,
undefined,
openTabsStates,
);
},
})
)
);

View File

@ -1,5 +1,5 @@
import { Collection } from "Contracts/ViewModels"; import { Collection } from "Contracts/ViewModels";
import create, { UseStore } from "zustand"; import { create } from "zustand";
interface TeachingBubbleState { interface TeachingBubbleState {
step: number; step: number;
@ -12,7 +12,7 @@ interface TeachingBubbleState {
setSampleCollection: (sampleCollection: Collection) => void; setSampleCollection: (sampleCollection: Collection) => void;
} }
export const useTeachingBubble: UseStore<TeachingBubbleState> = create((set) => ({ export const useTeachingBubble = create<TeachingBubbleState>((set) => ({
step: 1, step: 1,
isSampleDBExpanded: false, isSampleDBExpanded: false,
isDocumentsTabOpened: false, isDocumentsTabOpened: false,

View File

@ -1,5 +1,5 @@
import postRobot from "post-robot"; import postRobot from "post-robot";
import create, { UseStore } from "zustand"; import { create } from "zustand";
interface TerminalState { interface TerminalState {
terminalWindow: Window; terminalWindow: Window;
@ -7,7 +7,7 @@ interface TerminalState {
sendMessage: (message: string) => void; sendMessage: (message: string) => void;
} }
export const useTerminal: UseStore<TerminalState> = create((set, get) => ({ export const useTerminal = create<TerminalState>((set, get) => ({
terminalWindow: undefined, terminalWindow: undefined,
setTerminal: (terminalWindow: Window) => { setTerminal: (terminalWindow: Window) => {
set({ terminalWindow }); set({ terminalWindow });