From f8510659dee064d7b547e00a0264945029e58f8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:01:27 -0700 Subject: [PATCH 01/23] Bump typescript from 4.2.3 to 4.2.4 (#671) Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.2.3 to 4.2.4. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v4.2.3...v4.2.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74e9d525c..cc9097e0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21006,9 +21006,9 @@ } }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", "dev": true }, "typestyle": { diff --git a/package.json b/package.json index 6191b9430..970dbf699 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "ts-loader": "6.2.2", "tslint": "5.11.0", "tslint-microsoft-contrib": "6.0.0", - "typescript": "4.2.3", + "typescript": "4.2.4", "url-loader": "1.1.1", "wait-on": "4.0.2", "webpack": "4.43.0", From 19e39ea62f2d98e2b153186a0b59ec52e4894096 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:28:35 -0700 Subject: [PATCH 02/23] Bump ssri from 6.0.1 to 6.0.2 (#699) Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/npm/ssri/releases) - [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md) - [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc9097e0a..774a146ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19999,9 +19999,9 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "requires": { "minipass": "^3.1.1" } @@ -21886,9 +21886,9 @@ "dev": true }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" From 19cf2036066be477136b3dd478c7273a3faab00c Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Mon, 19 Apr 2021 12:58:53 -0700 Subject: [PATCH 03/23] Misc fixes from #500 (#701) * Ensure TabsBase.tabPath is always observable * In tests, Date.getTime() is not different enough * Add class name only in the Tab that needs it --- src/Explorer/Tabs/StoredProcedureTab.html | 2 +- src/Explorer/Tabs/TabsBase.ts | 9 +++------ src/Explorer/Tabs/TabsManager.html | 4 +--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Explorer/Tabs/StoredProcedureTab.html b/src/Explorer/Tabs/StoredProcedureTab.html index 93f985af8..dc7db1bf7 100644 --- a/src/Explorer/Tabs/StoredProcedureTab.html +++ b/src/Explorer/Tabs/StoredProcedureTab.html @@ -1,4 +1,4 @@ -
+
Stored Procedure Id
diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts index 3addaba4f..c49705ddc 100644 --- a/src/Explorer/Tabs/TabsBase.ts +++ b/src/Explorer/Tabs/TabsBase.ts @@ -14,6 +14,7 @@ import { TabsManager } from "./TabsManager"; // TODO: Use specific actions for logging telemetry data export default class TabsBase extends WaitsForTemplateViewModel { + private static id = 0; public static readonly component = { name: "tab", template: "" }; public closeTabButton: ViewModels.Button; public node: ViewModels.TreeNode; @@ -22,7 +23,7 @@ export default class TabsBase extends WaitsForTemplateViewModel { public rid: string; public hasFocus: ko.Observable; public isMouseOver: ko.Observable; - public tabId: string; + public tabId = `tab${TabsBase.id++}`; public tabKind: ViewModels.CollectionTabKind; public tabTitle: ko.Observable; public tabPath: ko.Observable; @@ -31,14 +32,11 @@ export default class TabsBase extends WaitsForTemplateViewModel { public isExecuting: ko.Observable; public pendingNotification?: ko.Observable; public manager?: TabsManager; - protected _theme: string; public onLoadStartKey: number; constructor(options: ViewModels.TabOptions) { super(); - const id = new Date().getTime().toString(); - this._theme = ThemeUtility.getMonacoTheme(options.theme); this.node = options.node; this.collection = options.collection; @@ -46,11 +44,10 @@ export default class TabsBase extends WaitsForTemplateViewModel { this.rid = options.rid || (this.collection && this.collection.rid) || ""; this.hasFocus = ko.observable(false); this.isMouseOver = ko.observable(false); - this.tabId = `tab${id}`; this.tabKind = options.tabKind; this.tabTitle = ko.observable(options.title); this.tabPath = - (options.tabPath && ko.observable(options.tabPath)) || + ko.observable(options.tabPath ?? "") || (this.collection && ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); this.isExecutionError = ko.observable(false); diff --git a/src/Explorer/Tabs/TabsManager.html b/src/Explorer/Tabs/TabsManager.html index a876cf38d..d8d27967d 100644 --- a/src/Explorer/Tabs/TabsManager.html +++ b/src/Explorer/Tabs/TabsManager.html @@ -85,9 +85,7 @@
- +
From f2585bba14b5b506fddd6bcd05dfd5f0f9d92f82 Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Mon, 19 Apr 2021 13:11:48 -0700 Subject: [PATCH 04/23] TabsManager in react (#500) --- src/Explorer/ComponentRegisterer.ts | 3 - src/Explorer/Tabs/Tabs.tsx | 119 ++++++++++++++++++++++++++++ src/Explorer/Tabs/TabsBase.ts | 42 ++-------- src/Explorer/Tabs/TabsManager.html | 93 ---------------------- src/Main.tsx | 5 +- src/hooks/useObservable.ts | 13 +++ src/hooks/useObservableState.ts | 16 ---- src/hooks/useTabs.ts | 8 +- 8 files changed, 147 insertions(+), 152 deletions(-) create mode 100644 src/Explorer/Tabs/Tabs.tsx delete mode 100644 src/Explorer/Tabs/TabsManager.html create mode 100644 src/hooks/useObservable.ts delete mode 100644 src/hooks/useObservableState.ts diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index 7c8b84c1a..e3311cb3f 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -20,7 +20,6 @@ import QueryTab from "./Tabs/QueryTab"; import QueryTablesTab from "./Tabs/QueryTablesTab"; import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2"; import StoredProcedureTab from "./Tabs/StoredProcedureTab"; -import TabsManagerTemplate from "./Tabs/TabsManager.html"; import TerminalTab from "./Tabs/TerminalTab"; import TriggerTab from "./Tabs/TriggerTab"; import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab"; @@ -34,7 +33,6 @@ ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); -ko.components.register("tabs-manager", { template: TabsManagerTemplate }); // Collection Tabs [ @@ -58,7 +56,6 @@ ko.components.register("tabs-manager", { template: TabsManagerTemplate }); // Panes ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); - ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent()); ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx new file mode 100644 index 000000000..f71ecec4a --- /dev/null +++ b/src/Explorer/Tabs/Tabs.tsx @@ -0,0 +1,119 @@ +import ko from "knockout"; +import React, { useEffect, useRef, useState } from "react"; +import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; +import errorIcon from "../../../images/close-black.svg"; +import { useObservable } from "../../hooks/useObservable"; +import TabsBase from "./TabsBase"; + +type Tab = TabsBase | (TabsBase & { render: () => JSX.Element }); + +export const Tabs = ({ tabs, activeTab }: { tabs: readonly Tab[]; activeTab: Tab }): JSX.Element => ( +
+
+
+ +
+
+ {...tabs.map((tab) => )} +
+
+
+); + +function TabNav({ tab, active }: { tab: Tab; active: boolean }) { + const [hovering, setHovering] = useState(false); + + return ( +
  • setHovering(true)} + onMouseLeave={() => setHovering(false)} + onClick={() => tab.onTabClick()} + onKeyPress={({ nativeEvent: e }) => tab.onKeyPressActivate(undefined, e)} + className={active ? "active tabList" : "tabList"} + title={useObservable(tab.tabPath)} + aria-selected={active} + aria-expanded={active} + aria-controls={tab.tabId} + tabIndex={0} + role="tab" + > + + +
    + + {useObservable(tab.isExecutionError) && } + {useObservable(tab.isExecuting) && ( + Loading + )} + + {useObservable(tab.tabTitle)} + + + +
    +
    +
    +
  • + ); +} + +const CloseButton = ({ tab, active, hovering }: { tab: Tab; active: boolean; hovering: boolean }) => ( + tab.onCloseTabButtonClick()} + tabIndex={active ? 0 : undefined} + onKeyPress={({ nativeEvent: e }) => tab.onKeyPressClose(undefined, e)} + > + + Close + + +); + +const ErrorIcon = ({ tab, active }: { tab: Tab; active: boolean }) => ( +
    tab.onErrorDetailsClick(undefined, e)} + onKeyPress={({ nativeEvent: e }) => tab.onErrorDetailsKeyPress(undefined, e)} + > + +
    +); + +function TabPane({ tab, active }: { tab: Tab; active: boolean }) { + const ref = useRef(); + const attrs = { + style: { display: active ? undefined : "none" }, + className: "tabs-container", + }; + + useEffect((): (() => void) | void => { + const { current: element } = ref; + if (element) { + ko.applyBindings(tab, element); + const ctx = ko.contextFor(element).createChildContext(tab); + ko.applyBindingsToDescendants(ctx, element); + return () => ko.cleanNode(element); + } + + if ("render" in tab) { + tab.isTemplateReady(true); + } + }, [ref, tab]); + + if ("render" in tab) { + return
    {tab.render()}
    ; + } + + return
    ; +} diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts index c49705ddc..b02065153 100644 --- a/src/Explorer/Tabs/TabsBase.ts +++ b/src/Explorer/Tabs/TabsBase.ts @@ -1,5 +1,4 @@ import * as ko from "knockout"; -import Q from "q"; import * as Constants from "../../Common/Constants"; import * as ThemeUtility from "../../Common/ThemeUtility"; import * as DataModels from "../../Contracts/DataModels"; @@ -21,15 +20,13 @@ export default class TabsBase extends WaitsForTemplateViewModel { public collection: ViewModels.CollectionBase; public database: ViewModels.Database; public rid: string; - public hasFocus: ko.Observable; - public isMouseOver: ko.Observable; public tabId = `tab${TabsBase.id++}`; public tabKind: ViewModels.CollectionTabKind; public tabTitle: ko.Observable; public tabPath: ko.Observable; public hashLocation: ko.Observable; - public isExecutionError: ko.Observable; - public isExecuting: ko.Observable; + public isExecutionError = ko.observable(false); + public isExecuting = ko.observable(false); public pendingNotification?: ko.Observable; public manager?: TabsManager; protected _theme: string; @@ -42,16 +39,12 @@ export default class TabsBase extends WaitsForTemplateViewModel { this.collection = options.collection; this.database = options.database; this.rid = options.rid || (this.collection && this.collection.rid) || ""; - this.hasFocus = ko.observable(false); - this.isMouseOver = ko.observable(false); this.tabKind = options.tabKind; this.tabTitle = ko.observable(options.title); this.tabPath = ko.observable(options.tabPath ?? "") || (this.collection && ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); - this.isExecutionError = ko.observable(false); - this.isExecuting = ko.observable(false); this.pendingNotification = ko.observable(undefined); this.onLoadStartKey = options.onLoadStartKey; this.hashLocation = ko.observable(options.hashLocation || ""); @@ -117,22 +110,12 @@ export default class TabsBase extends WaitsForTemplateViewModel { public onActivate(): void { this.updateSelectedNode(); - if (!!this.collection) { - this.collection.selectedSubnodeKind(this.tabKind); - } - - if (!!this.database) { - this.database.selectedSubnodeKind(this.tabKind); - } - - this.hasFocus(true); + this.collection?.selectedSubnodeKind(this.tabKind); + this.database?.selectedSubnodeKind(this.tabKind); this.updateGlobalHash(this.hashLocation()); - this.updateNavbarWithTabsButtons(); - TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, { tabName: this.constructor.name, - dataExplorerArea: Constants.Areas.Tab, tabTitle: this.tabTitle(), tabId: this.tabId, @@ -140,13 +123,8 @@ export default class TabsBase extends WaitsForTemplateViewModel { } public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => { - if (this.collection && this.collection.container) { - this.collection.container.expandConsole(); - } - - if (this.database && this.database.container) { - this.database.container.expandConsole(); - } + this.collection?.container?.expandConsole(); + this.database?.container?.expandConsole(); return false; }; @@ -159,9 +137,8 @@ export default class TabsBase extends WaitsForTemplateViewModel { return true; }; - public refresh(): Q.Promise { + public refresh() { location.reload(); - return Q(); } public getContainer(): Explorer { @@ -190,8 +167,3 @@ export default class TabsBase extends WaitsForTemplateViewModel { } }; } - -interface EditorPosition { - line: number; - column: number; -} diff --git a/src/Explorer/Tabs/TabsManager.html b/src/Explorer/Tabs/TabsManager.html deleted file mode 100644 index d8d27967d..000000000 --- a/src/Explorer/Tabs/TabsManager.html +++ /dev/null @@ -1,93 +0,0 @@ -
    - - - - - -
    - -
    - -
    - -
    - -
    diff --git a/src/Main.tsx b/src/Main.tsx index 9532b15a8..5f0c0bcc8 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -50,6 +50,7 @@ import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponen import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen"; import "./Explorer/SplashScreen/SplashScreen.less"; import "./Explorer/Tabs/QueryTab.less"; +import { Tabs } from "./Explorer/Tabs/Tabs"; import { useConfig } from "./hooks/useConfig"; import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer"; import { useSidePanel } from "./hooks/useSidePanel"; @@ -79,7 +80,7 @@ const App: React.FunctionComponent = () => { }; const { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel } = useSidePanel(); - const { tabs, tabsManager } = useTabs(); + const { tabs, activeTab, tabsManager } = useTabs(); const explorerParams: ExplorerParams = { setIsNotificationConsoleExpanded, @@ -205,7 +206,7 @@ const App: React.FunctionComponent = () => {
    {/* Collections Tree - End */} {tabs.length === 0 && } -
    +
    {/* Collections Tree and Tabs - End */}
    (observable: Pick, "subscribe" | "peek">): T { + const [, setValue] = useState(0); + + useEffect(() => { + const subscription = observable.subscribe(() => setValue((n) => 1 + n), undefined, "change"); + return () => subscription.dispose(); + }, [observable]); + + return observable.peek(); +} diff --git a/src/hooks/useObservableState.ts b/src/hooks/useObservableState.ts deleted file mode 100644 index 8894499b5..000000000 --- a/src/hooks/useObservableState.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isObservableArray, Observable, ObservableArray } from "knockout"; -import { useEffect, useState } from "react"; - -export function useObservableState(observable: Observable): [T, (s: T) => void]; -export function useObservableState(observable: ObservableArray): [T[], (s: T[]) => void]; -export function useObservableState(observable: ObservableArray | Observable): [T | T[], (s: T | T[]) => void] { - const [value, setValue] = useState(observable()); - - useEffect(() => { - isObservableArray(observable) - ? observable.subscribe((values) => setValue([...values])) - : observable.subscribe(setValue); - }, [observable]); - - return [value, observable]; -} diff --git a/src/hooks/useTabs.ts b/src/hooks/useTabs.ts index b487accbd..cd597bbf5 100644 --- a/src/hooks/useTabs.ts +++ b/src/hooks/useTabs.ts @@ -1,16 +1,18 @@ import { useState } from "react"; import TabsBase from "../Explorer/Tabs/TabsBase"; import { TabsManager } from "../Explorer/Tabs/TabsManager"; -import { useObservableState } from "./useObservableState"; +import { useObservable } from "./useObservable"; export type UseTabs = { tabs: readonly TabsBase[]; + activeTab: TabsBase; tabsManager: TabsManager; }; export function useTabs(): UseTabs { const [tabsManager] = useState(() => new TabsManager()); - const [tabs] = useObservableState(tabsManager.openedTabs); + const tabs = useObservable(tabsManager.openedTabs); + const activeTab = useObservable(tabsManager.activeTab); - return { tabs, tabsManager }; + return { tabs, activeTab, tabsManager }; } From 914e969083c5765924878ca217538c944908cd1e Mon Sep 17 00:00:00 2001 From: Tanuj Mittal Date: Mon, 19 Apr 2021 17:38:53 -0700 Subject: [PATCH 05/23] Add a feature flag to override Juno endpoint (#700) * Add a feature flag to override Juno endpoint * Fix build --- src/ConfigContext.ts | 15 ++++++++- src/Juno/JunoClient.test.ts | 43 +++++++++++++++++--------- src/Juno/JunoClient.ts | 17 ++++++++-- src/Platform/Hosted/extractFeatures.ts | 5 ++- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 654928c6b..dc7d5f887 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -26,6 +26,7 @@ export interface ConfigContext { GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. hostedExplorerURL: string; armAPIVersion?: string; + allowedJunoOrigins: string[]; } // Default configuration @@ -53,6 +54,13 @@ let configContext: Readonly = { GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306 JUNO_ENDPOINT: "https://tools.cosmos.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", + allowedJunoOrigins: [ + "https://juno-test.documents-dev.windows-int.net", + "https://juno-test2.documents-dev.windows-int.net", + "https://tools.cosmos.azure.com", + "https://tools-staging.cosmos.azure.com", + "https://localhost", + ], }; export function resetConfigContext(): void { @@ -86,13 +94,18 @@ export async function initializeConfiguration(): Promise { }); if (response.status === 200) { try { - const { allowedParentFrameOrigins, ...externalConfig } = await response.json(); + const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json(); Object.assign(configContext, externalConfig); if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) { updateConfigContext({ allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins], }); } + if (allowedJunoOrigins && allowedJunoOrigins.length > 0) { + updateConfigContext({ + allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins], + }); + } } catch (error) { console.error("Unable to parse json in config file"); console.error(error); diff --git a/src/Juno/JunoClient.test.ts b/src/Juno/JunoClient.test.ts index 8950e18f4..73e2a3cf8 100644 --- a/src/Juno/JunoClient.test.ts +++ b/src/Juno/JunoClient.test.ts @@ -1,10 +1,9 @@ import ko from "knockout"; import { HttpHeaders, HttpStatusCodes } from "../Common/Constants"; -import { IPinnedRepo, JunoClient, IPublishNotebookRequest } from "./JunoClient"; -import { configContext } from "../ConfigContext"; -import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { DatabaseAccount } from "../Contracts/DataModels"; import { updateUserContext, userContext } from "../UserContext"; +import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; +import { IPinnedRepo, IPublishNotebookRequest, JunoClient } from "./JunoClient"; const sampleSubscriptionId = "subscriptionId"; @@ -157,7 +156,7 @@ describe("Gallery", () => { const response = await junoClient.getSampleNotebooks(); expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/samples`, undefined); + expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/samples`, undefined); }); it("getPublicNotebooks", async () => { @@ -169,7 +168,7 @@ describe("Gallery", () => { const response = await junoClient.getPublicNotebooks(); expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/public`, undefined); + expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/public`, undefined); }); it("getNotebook", async () => { @@ -182,7 +181,7 @@ describe("Gallery", () => { const response = await junoClient.getNotebookInfo(id); expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}`); + expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}`); }); it("getNotebookContent", async () => { @@ -195,7 +194,7 @@ describe("Gallery", () => { const response = await junoClient.getNotebookContent(id); expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/content`); + expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}/content`); }); it("increaseNotebookViews", async () => { @@ -208,7 +207,7 @@ describe("Gallery", () => { const response = await junoClient.increaseNotebookViews(id); expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/views`, { + expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}/views`, { method: "PATCH", }); }); @@ -225,7 +224,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}/downloads`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery/${id}/downloads`, { method: "PATCH", headers: { @@ -248,7 +249,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}/favorite`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery/${id}/favorite`, { method: "PATCH", headers: { @@ -271,7 +274,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}/unfavorite`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery/${id}/unfavorite`, { method: "PATCH", headers: { @@ -293,7 +298,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/favorites`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery/favorites`, { headers: { [authorizationHeader.header]: authorizationHeader.token, @@ -314,7 +321,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/published`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery/published`, { headers: { [authorizationHeader.header]: authorizationHeader.token, @@ -336,7 +345,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery/${id}`, { method: "DELETE", headers: { @@ -365,7 +376,9 @@ describe("Gallery", () => { const authorizationHeader = getAuthorizationHeader(); expect(response.status).toBe(HttpStatusCodes.OK); expect(window.fetch).toBeCalledWith( - `${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery`, + `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ + sampleDatabaseAccount.name + }/gallery`, { method: "PUT", headers: { diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts index 751b40fe7..34020fc2d 100644 --- a/src/Juno/JunoClient.ts +++ b/src/Juno/JunoClient.ts @@ -7,7 +7,6 @@ import { IGitHubResponse } from "../GitHub/GitHubClient"; import { IGitHubOAuthToken } from "../GitHub/GitHubOAuthService"; import { userContext } from "../UserContext"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; -import { number } from "prop-types"; export interface IJunoResponse { status: number; @@ -484,8 +483,20 @@ export class JunoClient { }; } + // public for tests + public static getJunoEndpoint(): string { + const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; + if (configContext.allowedJunoOrigins.indexOf(new URL(junoEndpoint).origin) === -1) { + const error = `${junoEndpoint} not allowed as juno endpoint`; + console.error(error); + throw new Error(error); + } + + return junoEndpoint; + } + private getNotebooksUrl(): string { - return `${configContext.JUNO_ENDPOINT}/api/notebooks`; + return `${JunoClient.getJunoEndpoint()}/api/notebooks`; } private getAccount(): string { @@ -501,7 +512,7 @@ export class JunoClient { } private getAnalyticsUrl(): string { - return `${configContext.JUNO_ENDPOINT}/api/analytics`; + return `${JunoClient.getJunoEndpoint()}/api/analytics`; } private static getHeaders(): HeadersInit { diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 581317564..6921d6bdd 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -13,6 +13,7 @@ export type Features = { readonly enableTtl: boolean; readonly executeSproc: boolean; readonly hostedDataExplorer: boolean; + readonly junoEndpoint?: string; readonly livyEndpoint?: string; readonly notebookBasePath?: string; readonly notebookServerToken?: string; @@ -27,7 +28,8 @@ export type Features = { export function extractFeatures(given = new URLSearchParams(window.location.search)): Features { const downcased = new URLSearchParams(); const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value); - const get = (key: string, defaultValue?: string) => downcased.get("feature." + key) ?? defaultValue; + const get = (key: string, defaultValue?: string) => + downcased.get("feature." + key) ?? downcased.get(key) ?? defaultValue; try { new URLSearchParams(window.parent.location.search).forEach(set); @@ -52,6 +54,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear enableTtl: "true" === get("enablettl"), executeSproc: "true" === get("dataexplorerexecutesproc"), hostedDataExplorer: "true" === get("hosteddataexplorerenabled"), + junoEndpoint: get("junoendpoint"), livyEndpoint: get("livyendpoint"), notebookBasePath: get("notebookbasepath"), notebookServerToken: get("notebookservertoken"), From 2fd6305944335e338a6f8127b80c2d212605c40c Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Mon, 19 Apr 2021 22:08:25 -0500 Subject: [PATCH 06/23] Fix E2E tests. Add Playwright (#698) --- .github/workflows/ci.yml | 51 +- .gitignore | 4 +- CODING_GUIDELINES.md | 2 +- jest-playwright.config.js | 17 + jest-puppeteer.config.js | 12 - jest.config.e2e.js | 5 - jest.config.playwright.js | 7 + package-lock.json | 3946 +++++++++++++++-- package.json | 9 +- test/cassandra/container.spec.ts | 175 +- test/mongo/container.spec.ts | 209 +- test/mongo/mongoIndexPolicy.spec.ts | 106 - .../{testNotebooks => }/GettingStarted.ipynb | 0 test/notebooks/notebookTestUtils.ts | 52 - test/notebooks/upload.spec.ts | 27 + test/notebooks/uploadAndOpenNotebook.spec.ts | 28 - test/playwrightEnv.js | 26 + test/selfServe/selfServeExample.spec.ts | 63 +- test/sql/container.spec.ts | 194 +- test/sql/resourceToken.spec.ts | 109 +- test/tables/container.spec.ts | 128 +- test/utils/safeClick.ts | 11 + test/utils/shared.ts | 69 - utils/accesibilityCheck.js | 22 - utils/cleanupDBs.js | 40 +- 25 files changed, 3818 insertions(+), 1494 deletions(-) create mode 100644 jest-playwright.config.js delete mode 100644 jest-puppeteer.config.js delete mode 100644 jest.config.e2e.js create mode 100644 jest.config.playwright.js delete mode 100644 test/mongo/mongoIndexPolicy.spec.ts rename test/notebooks/{testNotebooks => }/GettingStarted.ipynb (100%) delete mode 100644 test/notebooks/notebookTestUtils.ts create mode 100644 test/notebooks/upload.spec.ts delete mode 100644 test/notebooks/uploadAndOpenNotebook.spec.ts create mode 100644 test/playwrightEnv.js create mode 100644 test/utils/safeClick.ts delete mode 100644 utils/accesibilityCheck.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c26bae620..574a8d613 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,58 +126,22 @@ jobs: with: name: screenshots path: failed-* - accessibility: - name: "Accessibility | Hosted" - needs: [lint, format, compile, unittest] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Use Node.js 14.x - uses: actions/setup-node@v1 - with: - node-version: 14.x - - name: Accessibility Check - run: | - # Ubuntu gets mad when webpack runs too many files watchers - cat /proc/sys/fs/inotify/max_user_watches - sudo sysctl fs.inotify.max_user_watches=524288 - sudo sysctl -p - npm ci - npm start & - npx wait-on -i 5000 https-get://0.0.0.0:1234/ - node utils/accesibilityCheck.js - shell: bash - env: - NODE_TLS_REJECT_UNAUTHORIZED: 0 - endtoendhosted: - name: "End to End Tests" + endtoend: + name: "E2E" needs: [cleanupaccounts] runs-on: ubuntu-latest env: NODE_TLS_REJECT_UNAUTHORIZED: 0 - PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }} - PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }} - PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }} - PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }} - PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }} - PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }} - NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }} - NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }} NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} - PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }} - MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }} - CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }} - TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }} - DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html" strategy: fail-fast: false matrix: test-file: - ./test/cassandra/container.spec.ts - - ./test/mongo/mongoIndexPolicy.spec.ts - - ./test/notebooks/uploadAndOpenNotebook.spec.ts - - ./test/selfServe/selfServeExample.spec.ts - ./test/sql/container.spec.ts + - ./test/mongo/container.spec.ts + - ./test/selfServe/selfServeExample.spec.ts + - ./test/notebooks/upload.spec.ts - ./test/sql/resourceToken.spec.ts - ./test/tables/container.spec.ts steps: @@ -188,16 +152,15 @@ jobs: node-version: 14.x - run: npm ci - run: npm start & - - run: node utils/cleanupDBs.js - run: npm run wait-for-server - name: ${{ matrix['test-file'] }} - run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }} + run: npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} shell: bash - uses: actions/upload-artifact@v2 if: failure() with: name: screenshots - path: failed-* + path: screenshots/ cleanupaccounts: name: "Cleanup Test Database Accounts" runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 109e6c4d2..be016240b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ Contracts/* .DS_Store .cache/ .env -failure.png \ No newline at end of file +failure.png +screenshots/* +GettingStarted-ignore*.ipynb \ No newline at end of file diff --git a/CODING_GUIDELINES.md b/CODING_GUIDELINES.md index daa14a470..bad2583ab 100644 --- a/CODING_GUIDELINES.md +++ b/CODING_GUIDELINES.md @@ -153,7 +153,7 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul ✅ DO -- Use [Puppeteer](https://developers.google.com/web/tools/puppeteer) and [Jest](https://jestjs.io/) +- Use [Playwright](https://github.com/microsoft/playwright) and [Jest](https://jestjs.io/) - Write or modify an existing E2E test that covers the primary use case of any major feature. - Use caution. Do not try to cover every case. End to End tests can be slow and brittle. diff --git a/jest-playwright.config.js b/jest-playwright.config.js new file mode 100644 index 000000000..53103b830 --- /dev/null +++ b/jest-playwright.config.js @@ -0,0 +1,17 @@ +const isCI = require("is-ci"); + +module.exports = { + exitOnPageError: false, + launchOptions: { + headless: isCI, + slowMo: 10, + timeout: 60000, + }, + contextOptions: { + ignoreHTTPSErrors: true, + viewport: { + width: 1920, + height: 1080, + }, + }, +}; diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js deleted file mode 100644 index 804804256..000000000 --- a/jest-puppeteer.config.js +++ /dev/null @@ -1,12 +0,0 @@ -const isCI = require("is-ci"); - -module.exports = { - launch: { - headless: isCI, - slowMo: 55, - defaultViewport: null, - ignoreHTTPSErrors: true, - args: ["--disable-web-security"], - exitOnPageError: false, - }, -}; diff --git a/jest.config.e2e.js b/jest.config.e2e.js deleted file mode 100644 index 38970f58e..000000000 --- a/jest.config.e2e.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - preset: "jest-puppeteer", - testMatch: ["/test/**/*.spec.[jt]s?(x)"], - setupFiles: ["dotenv/config"], -}; diff --git a/jest.config.playwright.js b/jest.config.playwright.js new file mode 100644 index 000000000..c452a5368 --- /dev/null +++ b/jest.config.playwright.js @@ -0,0 +1,7 @@ +module.exports = { + preset: "jest-playwright-preset", + testMatch: ["/test/**/*.spec.[jt]s?(x)"], + setupFiles: ["dotenv/config"], + testEnvironment: "./test/playwrightEnv.js", + setupFilesAfterEnv: ["expect-playwright"], +}; diff --git a/package-lock.json b/package-lock.json index 774a146ee..99581af0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2183,57 +2183,18 @@ "tslib": "^1.10.0" } }, - "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", - "dev": true - }, - "@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==", - "dev": true - }, "@hapi/formula": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", "dev": true }, - "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", - "dev": true - }, - "@hapi/joi": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", - "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", - "dev": true, - "requires": { - "@hapi/address": "2.x.x", - "@hapi/bourne": "1.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/topo": "3.x.x" - } - }, "@hapi/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==", "dev": true }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "dev": true, - "requires": { - "@hapi/hoek": "^8.3.0" - } - }, "@icons/material": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", @@ -4519,6 +4480,35 @@ "@phosphor/virtualdom": "^1.2.0" } }, + "@sideway/address": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.1.tgz", + "integrity": "sha512-+I5aaQr3m0OAmMr7RQ3fR9zx55sejEYR2BFJaxL+zT3VM2611X0SHvPWIbAUBZVTn/YzYKbV8gJ2oT/QELknfQ==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==", + "dev": true + } + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -4528,6 +4518,15 @@ "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "@sinonjs/formatio": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", @@ -5115,16 +5114,6 @@ "@types/enzyme": "*" } }, - "@types/expect-puppeteer": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.5.tgz", - "integrity": "sha512-vxPaumA8Fj6xlm3llKCR9V8L936HX4PyipaNMxDbWQIOWZoCl99jabD/6xuxXnCptOWUdUhXwDuX5cAJgCHsLg==", - "dev": true, - "requires": { - "@types/jest": "*", - "@types/puppeteer": "*" - } - }, "@types/geojson": { "version": "7946.0.7", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz", @@ -5324,17 +5313,6 @@ } } }, - "@types/jest-environment-puppeteer": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.1.tgz", - "integrity": "sha512-LiZTD6i63le6QMnxi7pJB0SFv/fWtss6VVEEDm/UaeowBgjduf8txyE//j3WEeDPxngTvioUjbzA7Rc6Wc3cBA==", - "dev": true, - "requires": { - "@jest/types": ">=24 <=26", - "@types/puppeteer": "*", - "jest-environment-node": ">=24 <=26" - } - }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -5421,15 +5399,6 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz", "integrity": "sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw==" }, - "@types/puppeteer": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", - "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz", @@ -5635,6 +5604,12 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==" }, + "@types/wait-on": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.2.0.tgz", + "integrity": "sha512-3+jsMyPm8aot1mqDUDLOl+dejPvpysUUoUXD6CCRY20MNNhcjEfvdcBnGdnk7DEYs9Hr16ubGJA/9/QW0Df/9g==", + "dev": true + }, "@types/webpack": { "version": "4.41.26", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz", @@ -6602,6 +6577,15 @@ "normalize-path": "^2.1.1" } }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, "applicationinsights": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.8.0.tgz", @@ -7268,6 +7252,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "optional": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -7278,6 +7263,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "optional": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -7287,6 +7273,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7714,6 +7701,47 @@ "unset-value": "^1.0.0" } }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + } + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -7991,6 +8019,12 @@ "safe-buffer": "^5.0.1" } }, + "cjs-module-lexer": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", + "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -8119,39 +8153,6 @@ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", - "dev": true, - "requires": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" - }, - "dependencies": { - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "cls-hooked": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", @@ -9319,6 +9320,12 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -9333,6 +9340,12 @@ "mimic-response": "^2.0.0" } }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, "deep-diff": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", @@ -9392,6 +9405,23 @@ "ip-regex": "^2.1.0" } }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -9581,12 +9611,6 @@ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, - "devtools-protocol": { - "version": "0.0.854822", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz", - "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==", - "dev": true - }, "diagnostic-channel": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.3.1.tgz", @@ -9883,6 +9907,12 @@ "shimmer": "^1.2.0" } }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", + "dev": true + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -10095,6 +10125,12 @@ "next-tick": "~1.0.0" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", @@ -10706,10 +10742,10 @@ "jest-regex-util": "^24.9.0" } }, - "expect-puppeteer": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.4.0.tgz", - "integrity": "sha512-6Ey4Xy2xvmuQu7z7YQtMsaMV0EHJRpVxIDOd5GRrm04/I3nkTKIutELfECsLp6le+b3SSa3cXhPiw6PgqzxYWA==", + "expect-playwright": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.3.3.tgz", + "integrity": "sha512-uoeyx2D5LawJdziMdweOp6cnZzFOOPT9VvPG6gOh6YC7N9pU0k2KpVlRiz/Vc/fFBiGUNNeJq2Aq+9GJ65Nfrw==", "dev": true }, "expose-loader": { @@ -11492,6 +11528,59 @@ "for-in": "^1.0.1" } }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -11556,10 +11645,17 @@ "readable-stream": "^2.0.0" } }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true }, "fs-exists-sync": { "version": "0.1.0", @@ -12160,6 +12256,24 @@ "minimalistic-assert": "^1.0.1" } }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + } + } + }, "hasher": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hasher/-/hasher-1.2.0.tgz", @@ -13007,6 +13121,12 @@ "isobject": "^3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", @@ -13118,6 +13238,15 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, "istanbul-lib-instrument": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", @@ -13139,6 +13268,94 @@ } } }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "istanbul-lib-report": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", @@ -13399,18 +13616,18 @@ "dev": true }, "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" }, "dependencies": { "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } @@ -13431,9 +13648,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -14600,6 +14817,1263 @@ "throat": "^4.0.0" } }, + "jest-circus": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.3.tgz", + "integrity": "sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "stack-utils": "^2.0.2", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/console": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.6.2", + "jest-util": "^26.6.2", + "slash": "^3.0.0" + } + }, + "@jest/environment": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", + "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + } + }, + "@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" + } + }, + "@jest/source-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "dev": true, + "requires": { + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" + } + }, + "@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/prettier": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", + "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "acorn": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.1.tgz", + "integrity": "sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "babel-jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "dev": true, + "requires": { + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", + "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", + "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + }, + "expect": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "jest-config": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", + "@jest/types": "^26.6.2", + "babel-jest": "^26.6.3", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" + } + }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2" + } + }, + "jest-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" + } + }, + "jest-leak-detector": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "dev": true, + "requires": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-mock": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*" + } + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "jest-runner": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.6.2", + "jest-leak-detector": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + } + }, + "jest-runtime": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/globals": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^0.6.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.4.1" + } + }, + "jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "jest-validate": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", + "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "leven": "^3.1.0", + "pretty-format": "^26.6.2" + } + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "jsdom": { + "version": "16.5.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.3.tgz", + "integrity": "sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.1.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.4", + "xml-name-validator": "^3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-url": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + } + } + }, "jest-config": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", @@ -14648,85 +16122,6 @@ } } }, - "jest-dev-server": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.4.0.tgz", - "integrity": "sha512-STEHJ3iPSC8HbrQ3TME0ozGX2KT28lbT4XopPxUm2WimsX3fcB3YOptRh12YphQisMhfqNSNTZUmWyT3HEXS2A==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "cwd": "^0.10.0", - "find-process": "^1.4.3", - "prompts": "^2.3.0", - "spawnd": "^4.4.0", - "tree-kill": "^1.2.2", - "wait-on": "^3.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "wait-on": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", - "integrity": "sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==", - "dev": true, - "requires": { - "@hapi/joi": "^15.0.3", - "core-js": "^2.6.5", - "minimist": "^1.2.0", - "request": "^2.88.0", - "rx": "^4.1.0" - } - } - } - }, "jest-diff": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", @@ -14783,69 +16178,6 @@ "jest-util": "^24.9.0" } }, - "jest-environment-puppeteer": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.0.tgz", - "integrity": "sha512-iV8S8+6qkdTM6OBR/M9gKywEk8GDSOe05hspCs5D8qKSwtmlUfdtHfB4cakdc68lC6YfK3AUsLirpfgodCHjzQ==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "cwd": "^0.10.0", - "jest-dev-server": "^4.4.0", - "merge-deep": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "jest-get-type": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", @@ -14936,19 +16268,1384 @@ "@jest/types": "^24.9.0" } }, + "jest-playwright-preset": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jest-playwright-preset/-/jest-playwright-preset-1.5.1.tgz", + "integrity": "sha512-zsFAe61V72vSLkd1fCcf7YbHmbdAB82SLBdUuCUF43aODIojshQEDF88KdWL9P+4JQ+DvEABT+6sFX4sY0rR2w==", + "dev": true, + "requires": { + "expect-playwright": "^0.3.3", + "jest-circus": "^26.6.3", + "jest-environment-node": "^26.6.2", + "jest-process-manager": "^0.2.9", + "jest-runner": "^26.6.3", + "nyc": "^15.1.0", + "playwright-core": ">=1.2.0", + "rimraf": "^3.0.2", + "uuid": "^8.3.2" + }, + "dependencies": { + "@jest/console": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.6.2", + "jest-util": "^26.6.2", + "slash": "^3.0.0" + } + }, + "@jest/environment": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", + "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + } + }, + "@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" + } + }, + "@jest/source-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "dev": true, + "requires": { + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" + } + }, + "@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/prettier": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", + "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "acorn": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.1.tgz", + "integrity": "sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "babel-jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "dev": true, + "requires": { + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", + "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", + "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + }, + "expect": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "jest-config": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", + "@jest/types": "^26.6.2", + "babel-jest": "^26.6.3", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" + } + }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2" + } + }, + "jest-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" + } + }, + "jest-leak-detector": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "dev": true, + "requires": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-mock": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*" + } + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "jest-runner": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.6.2", + "jest-leak-detector": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + } + }, + "jest-runtime": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/globals": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^0.6.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.4.1" + } + }, + "jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "jest-validate": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", + "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "leven": "^3.1.0", + "pretty-format": "^26.6.2" + } + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "jsdom": { + "version": "16.5.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.3.tgz", + "integrity": "sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.1.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.4", + "xml-name-validator": "^3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true + }, + "playwright-core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.10.0.tgz", + "integrity": "sha512-SDA5KPwnJJSfnNX/b7h8y0ChwBmcbbcCofYXkZGMVuzXZsmHPGLOBRhgkwN2nzJ10Ezf4cd1OcVOeOLKPxjeRg==", + "dev": true, + "requires": { + "commander": "^6.1.0", + "debug": "^4.1.1", + "extract-zip": "^2.0.1", + "https-proxy-agent": "^5.0.0", + "jpeg-js": "^0.4.2", + "mime": "^2.4.6", + "pngjs": "^5.0.0", + "progress": "^2.0.3", + "proper-lockfile": "^4.1.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "stack-utils": "^2.0.3", + "ws": "^7.3.1" + } + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-url": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + } + } + }, "jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" }, - "jest-puppeteer": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.4.0.tgz", - "integrity": "sha512-ZaiCTlPZ07B9HW0erAWNX6cyzBqbXMM7d2ugai4epBDKpKvRDpItlRQC6XjERoJELKZsPziFGS0OhhUvTvQAXA==", + "jest-process-manager": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.2.9.tgz", + "integrity": "sha512-IKVdOSz1NLwKg9HTeyEDn63waMvKK6wcS+tCarGquNIktlXt4zAW2cfJ9vAA/xBcidWYKOPXHvy1l1N8qfw3Ww==", "dev": true, "requires": { - "expect-puppeteer": "^4.4.0", - "jest-environment-puppeteer": "^4.4.0" + "@types/wait-on": "^5.2.0", + "chalk": "^4.1.0", + "cwd": "^0.10.0", + "exit": "^0.1.2", + "find-process": "^1.4.4", + "prompts": "^2.4.0", + "signal-exit": "^3.0.3", + "spawnd": "^4.4.0", + "tree-kill": "^1.2.2", + "wait-on": "^5.2.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wait-on": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", + "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", + "dev": true, + "requires": { + "axios": "^0.21.1", + "joi": "^17.3.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^6.6.3" + } + } } }, "jest-regex-util": { @@ -15178,6 +17875,42 @@ } } }, + "joi": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", + "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.0", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==", + "dev": true + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + } + } + }, + "jpeg-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", + "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==", + "dev": true + }, "jquery": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", @@ -15467,12 +18200,6 @@ "resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz", "integrity": "sha1-xsxaNA6N80DrM1YzaD6lm4KMMi0=" }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -16027,28 +18754,6 @@ "is-what": "^3.3.1" } }, - "merge-deep": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", - "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -16272,24 +18977,6 @@ } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -16298,7 +18985,8 @@ "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "optional": true }, "moment": { "version": "2.29.1", @@ -16593,6 +19281,15 @@ "requires": { "inherits": "2.0.3" } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -16620,6 +19317,15 @@ } } }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, "node-releases": { "version": "1.1.70", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", @@ -16708,6 +19414,302 @@ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -17171,6 +20173,15 @@ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -17216,6 +20227,18 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -17509,6 +20532,59 @@ "find-up": "^3.0.0" } }, + "playwright": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.10.0.tgz", + "integrity": "sha512-b7SGBcCPq4W3pb4ImEDmNXtO0ZkJbZMuWiShsaNJd+rGfY/6fqwgllsAojmxGSgFmijYw7WxCoPiAIEDIH16Kw==", + "dev": true, + "requires": { + "commander": "^6.1.0", + "debug": "^4.1.1", + "extract-zip": "^2.0.1", + "https-proxy-agent": "^5.0.0", + "jpeg-js": "^0.4.2", + "mime": "^2.4.6", + "pngjs": "^5.0.0", + "progress": "^2.0.3", + "proper-lockfile": "^4.1.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "stack-utils": "^2.0.3", + "ws": "^7.3.1" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + } + } + }, "plotly.js-cartesian-dist-min": { "version": "1.52.3", "resolved": "https://registry.npmjs.org/plotly.js-cartesian-dist-min/-/plotly.js-cartesian-dist-min-1.52.3.tgz", @@ -17524,6 +20600,12 @@ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" }, + "pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "dev": true + }, "polygon-offset": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/polygon-offset/-/polygon-offset-0.3.1.tgz", @@ -17822,6 +20904,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -17873,6 +20964,17 @@ "reflect.ownkeys": "^0.2.0" } }, + "proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -17959,91 +21061,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "puppeteer": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz", - "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.854822", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "pure-color": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", @@ -18702,6 +21719,15 @@ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "remark-parse": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", @@ -19083,12 +22109,6 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true - }, "rx-jupyter": { "version": "5.5.12", "resolved": "https://registry.npmjs.org/rx-jupyter/-/rx-jupyter-5.5.12.tgz", @@ -19526,35 +22546,6 @@ "safe-buffer": "^5.0.1" } }, - "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "dev": true, - "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^2.0.1", - "lazy-cache": "^0.2.3", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "requires": { - "is-buffer": "^1.0.2" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", - "dev": true - } - } - }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -19888,6 +22879,46 @@ "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "spawnd": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", @@ -20437,6 +23468,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "optional": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -20448,6 +23480,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "optional": true, "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -20460,6 +23493,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -20479,18 +23513,18 @@ }, "dependencies": { "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" } }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } @@ -21049,28 +24083,6 @@ "resolved": "https://registry.npmjs.org/uglify-save-license/-/uglify-save-license-0.4.1.tgz", "integrity": "sha1-lXJsF8xv0XHDYX479NjYKqjEzOE=" }, - "unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", diff --git a/package.json b/package.json index 970dbf699..e7706691e 100644 --- a/package.json +++ b/package.json @@ -111,15 +111,12 @@ "@types/d3": "5.9.2", "@types/enzyme": "3.10.7", "@types/enzyme-adapter-react-16": "1.0.6", - "@types/expect-puppeteer": "4.4.5", "@types/hasher": "0.0.31", "@types/jest": "26.0.20", - "@types/jest-environment-puppeteer": "4.4.1", "@types/memoize-one": "4.1.1", "@types/node": "12.11.1", "@types/promise.prototype.finally": "2.0.3", "@types/prop-types": "15.5.8", - "@types/puppeteer": "5.4.3", "@types/q": "1.5.1", "@types/react": "17.0.3", "@types/react-dom": "17.0.3", @@ -131,7 +128,6 @@ "@types/underscore": "1.7.36", "@typescript-eslint/eslint-plugin": "4.0.1", "@typescript-eslint/parser": "4.0.1", - "axe-puppeteer": "1.1.0", "babel-jest": "24.9.0", "babel-loader": "8.1.0", "buffer": "5.1.0", @@ -146,6 +142,7 @@ "eslint-plugin-no-null": "1.0.2", "eslint-plugin-prefer-arrow": "1.2.2", "eslint-plugin-react-hooks": "4.2.0", + "expect-playwright": "0.3.3", "expose-loader": "0.7.5", "fast-glob": "3.2.5", "file-loader": "2.0.0", @@ -155,7 +152,7 @@ "html-webpack-plugin": "3.2.0", "jest": "25.5.4", "jest-canvas-mock": "2.1.0", - "jest-puppeteer": "4.4.0", + "jest-playwright-preset": "1.5.1", "jest-trx-results-processor": "0.0.7", "less": "3.8.1", "less-loader": "4.1.0", @@ -163,8 +160,8 @@ "mini-css-extract-plugin": "0.4.3", "monaco-editor-webpack-plugin": "1.7.0", "node-fetch": "2.6.1", + "playwright": "1.10.0", "prettier": "2.2.1", - "puppeteer": "8.0.0", "raw-loader": "0.5.1", "rimraf": "3.0.0", "sinon": "3.2.1", diff --git a/test/cassandra/container.spec.ts b/test/cassandra/container.spec.ts index 10925b18b..45ee23ce7 100644 --- a/test/cassandra/container.spec.ts +++ b/test/cassandra/container.spec.ts @@ -1,148 +1,35 @@ -import "expect-puppeteer"; -import { Frame } from "puppeteer"; -import { generateUniqueName, login } from "../utils/shared"; +import { jest } from "@jest/globals"; +import "expect-playwright"; +import { safeClick } from "../utils/safeClick"; +import { generateUniqueName } from "../utils/shared"; +jest.setTimeout(120000); -jest.setTimeout(300000); -const RENDER_DELAY = 800; -const RETRY_DELAY = 5000; -const CREATE_DELAY = 10000; -const LOADING_STATE_DELAY = 2500; +test("Cassandra keyspace and table CRUD", async () => { + const keyspaceId = generateUniqueName("keyspace"); + const tableId = generateUniqueName("table"); -describe("Collection Add and Delete Cassandra spec", () => { - it("creates a collection", async () => { - try { - const keyspaceId = generateUniqueName("key"); - const tableId = generateUniqueName("tab"); - const frame = await login(process.env.CASSANDRA_CONNECTION_STRING); - - // create new table - await frame.waitFor('button[data-test="New Table"]', { visible: true }); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.click('button[data-test="New Table"]'); - - // type keyspace id - await frame.waitFor('input[id="keyspace-id"]', { visible: true }); - await frame.type('input[id="keyspace-id"]', keyspaceId); - - // type table id - await frame.waitFor('input[class="textfontclr"]'); - await frame.type('input[class="textfontclr"]', tableId); - - // click submit - await frame.waitFor("#cassandraaddcollectionpane > div > form > div.paneFooter > div > input"); - await frame.click("#cassandraaddcollectionpane > div > form > div.paneFooter > div > input"); - - // open database menu - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`); - const selectedDbId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, databases[0]); - - await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true }; - await frame.waitFor(CREATE_DELAY); - await frame.waitFor("div[class='rowData'] > span[class='message']"); - - const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", (elements) => { - return elements.some((el) => el.textContent.includes("Successfully created")); - }); - - expect(didCreateContainer).toBe(true); - - await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - - await clickDBMenu(selectedDbId, frame); - - const collections = await frame.$$( - `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]` - ); - - if (collections.length) { - await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, { - visible: true, - }); - - const textId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, collections[0]); - await frame.waitFor(`div[data-test="${textId}"]`, { visible: true }); - // delete container - - // click context menu for container - await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true }); - await frame.click(`div[data-test="${textId}"] > div > button`); - - // click delete container - await frame.waitFor(RENDER_DELAY); - await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - - // confirm delete container - await frame.waitFor('input[id="confirmCollectionId"]', { visible: true }); - await frame.type('input[id="confirmCollectionId"]', textId); - - // click delete - await frame.waitFor('button[id="sidePanelOkButton"]', { visible: true }); - await frame.click('button[id="sidePanelOkButton"]'); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - await expect(page).not.toMatchElement(`div[data-test="${textId}"]`); - } - - // click context menu for database - await frame.waitFor(`div[data-test="${keyspaceId}"] > div > button`); - await frame.waitFor(RENDER_DELAY); - const button = await frame.$(`div[data-test="${keyspaceId}"] > div > button`); - await button.focus(); - await button.asElement().click(); - - // click delete database - await frame.waitFor(RENDER_DELAY); - const dbElements = await frame.$$('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]'); - await dbElements[0].click(); - - // confirm delete database - await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true }); - await frame.waitFor(RENDER_DELAY); - await frame.type('input[id="confirmDatabaseId"]', keyspaceId.trim()); - - // click delete - await frame.click('button[id="sidePanelOkButton"]'); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await expect(page).not.toMatchElement(`div[data-test="${keyspaceId}"]`); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `failed-${testName}.jpg` }); - throw error; - } + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner"); + await page.waitForSelector("iframe"); + const explorer = page.frame({ + name: "explorer", }); + + await explorer.click('[data-test="New Table"]'); + await explorer.click('[data-test="addCollection-keyspaceId"]'); + await explorer.fill('[data-test="addCollection-keyspaceId"]', keyspaceId); + await explorer.click('[data-test="addCollection-tableId"]'); + await explorer.fill('[data-test="addCollection-tableId"]', tableId); + await explorer.click('[aria-label="Add Table"] [data-test="addCollection-createCollection"]'); + await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`); + await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`); + await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")'); + await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId); + await explorer.click('[aria-label="Submit"]'); + await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More"]`); + await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")'); + await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); + await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', keyspaceId); + await explorer.click("#sidePanelOkButton"); + await expect(explorer).not.toHaveText(".dataResourceTree", keyspaceId); + await expect(explorer).not.toHaveText(".dataResourceTree", tableId); }); - -async function clickDBMenu(dbId: string, frame: Frame, retries = 0) { - const button = await frame.$(`div[data-test="${dbId}"]`); - await button.focus(); - const handler = await button.asElement(); - await handler.click(); - await ensureMenuIsOpen(dbId, frame, retries); - return button; -} - -async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) { - await frame.waitFor(RETRY_DELAY); - const button = await frame.$(`div[data-test="${dbId}"]`); - const classList = await frame.evaluate((button) => { - return button.parentElement.classList; - }, button); - if (!Object.values(classList).includes("selected") && retries < 5) { - retries = retries + 1; - await clickDBMenu(dbId, frame, retries); - } -} diff --git a/test/mongo/container.spec.ts b/test/mongo/container.spec.ts index 63f86dd12..11aa71862 100644 --- a/test/mongo/container.spec.ts +++ b/test/mongo/container.spec.ts @@ -1,164 +1,53 @@ -import "expect-puppeteer"; -import { Frame } from "puppeteer"; -import { generateDatabaseName, generateUniqueName, login } from "../utils/shared"; +import { jest } from "@jest/globals"; +import "expect-playwright"; +import { safeClick } from "../utils/safeClick"; +import { generateUniqueName } from "../utils/shared"; +jest.setTimeout(240000); -jest.setTimeout(300000); -const LOADING_STATE_DELAY = 2500; -const RETRY_DELAY = 5000; -const CREATE_DELAY = 10000; -const RENDER_DELAY = 1000; +test("SQL CRUD", async () => { + const databaseId = generateUniqueName("db"); + const containerId = generateUniqueName("container"); -describe("Collection Add and Delete Mongo spec", () => { - it("creates a collection", async () => { - try { - const dbId = generateDatabaseName(); - const collectionId = generateUniqueName("col"); - const sharedKey = `${generateUniqueName()}`; - const frame = await login(process.env.MONGO_CONNECTION_STRING); - - // create new collection - await frame.waitFor('button[data-test="New Collection"]', { visible: true }); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.click('button[data-test="New Collection"]'); - - // check new database - await frame.waitFor('input[data-test="addCollection-createNewDatabase"]'); - await frame.click('input[data-test="addCollection-createNewDatabase"]'); - - // check shared throughput - await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]'); - await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]'); - - // type database id - await frame.waitFor('input[data-test="addCollection-newDatabaseId"]'); - const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]'); - await dbInput.press("Backspace"); - await dbInput.type(dbId); - - // type collection id - await frame.waitFor('input[data-test="addCollection-collectionId"]'); - const input = await frame.$('input[data-test="addCollection-collectionId"]'); - await input.press("Backspace"); - await input.type(collectionId); - - // type partition key value - await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]'); - const keyInput = await frame.$('input[data-test="addCollection-partitionKeyValue"]'); - await keyInput.press("Backspace"); - await keyInput.type(sharedKey); - - // click submit - await frame.waitFor("#submitBtnAddCollection"); - await frame.click("#submitBtnAddCollection"); - - // validate created - // open database menu - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`); - const selectedDbId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, databases[0]); - - await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true }; - await frame.waitFor(CREATE_DELAY); - await frame.waitFor("div[class='rowData'] > span[class='message']"); - - const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", (elements) => { - return elements.some((el) => el.textContent.includes("Successfully created")); - }); - - expect(didCreateContainer).toBe(true); - - await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - - await clickDBMenu(selectedDbId, frame); - - const collections = await frame.$$( - `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]` - ); - - if (collections.length) { - const textId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, collections[0]); - await frame.waitFor(`div[data-test="${textId}"]`, { visible: true }); - // delete container - - // click context menu for container - await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true }); - await frame.click(`div[data-test="${textId}"] > div > button`); - - // click delete container - await frame.waitFor(RENDER_DELAY); - await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - - // confirm delete container - await frame.waitFor('input[id="confirmCollectionId"]', { visible: true }); - await frame.type('input[id="confirmCollectionId"]', textId); - - // click delete - await frame.waitFor('button[id="sidePanelOkButton"]', { visible: true }); - await frame.click('button[id="sidePanelOkButton"]'); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - await expect(page).not.toMatchElement(`div[data-test="${textId}"]`); - } - - // click context menu for database - await frame.waitFor(`div[data-test="${selectedDbId}"] > div > button`); - await frame.waitFor(RENDER_DELAY); - const button = await frame.$(`div[data-test="${selectedDbId}"] > div > button`); - await button.focus(); - await button.asElement().click(); - - // click delete database - await frame.waitFor(RENDER_DELAY); - await frame.waitFor('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]'); - await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]'); - - // confirm delete database - await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true }); - await frame.waitFor(RENDER_DELAY); - await frame.type('input[id="confirmDatabaseId"]', selectedDbId); - - // click delete - await frame.click('button[id="sidePanelOkButton"]'); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await expect(page).not.toMatchElement(`div[data-test="${selectedDbId}"]`); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `failed-${testName}.jpg` }); - throw error; - } + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner"); + await page.waitForSelector("iframe"); + const explorer = page.frame({ + name: "explorer", }); + + // Create new database and collection + await explorer.click('[data-test="New Collection"]'); + await explorer.click('[data-test="addCollection-newDatabaseId"]'); + await explorer.fill('[data-test="addCollection-newDatabaseId"]', databaseId); + await explorer.click('[data-test="addCollection-collectionId"]'); + await explorer.fill('[data-test="addCollection-collectionId"]', containerId); + await explorer.click('[data-test="addCollection-collectionId"]'); + await explorer.fill('[data-test="addCollection-collectionId"]', containerId); + await explorer.click('[data-test="addCollection-partitionKeyValue"]'); + await explorer.fill('[data-test="addCollection-partitionKeyValue"]', "/pk"); + await explorer.click('[data-test="addCollection-createCollection"]'); + await safeClick(explorer, `.nodeItem >> text=${databaseId}`); + await safeClick(explorer, `.nodeItem >> text=${containerId}`); + // Create indexing policy + await safeClick(explorer, ".nodeItem >> text=Settings"); + await explorer.click('button[role="tab"]:has-text("Indexing Policy")'); + await explorer.click('[aria-label="Index Field Name 0"]'); + await explorer.fill('[aria-label="Index Field Name 0"]', "foo"); + await explorer.click("text=Select an index type"); + await explorer.click('button[role="option"]:has-text("Single Field")'); + await explorer.click('[data-test="Save"]'); + // Remove indexing policy + await explorer.click('[aria-label="Delete index Button"]'); + await explorer.click('[data-test="Save"]'); + // Delete database and collection + await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`); + await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Collection")'); + await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId); + await explorer.click('[aria-label="Submit"]'); + await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`); + await explorer.click('button[role="menuitem"]:has-text("Delete Database")'); + await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); + await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId); + await explorer.click("#sidePanelOkButton"); + await expect(explorer).not.toHaveText(".dataResourceTree", databaseId); + await expect(explorer).not.toHaveText(".dataResourceTree", containerId); }); - -async function clickDBMenu(dbId: string, frame: Frame, retries = 0) { - const button = await frame.$(`div[data-test="${dbId}"]`); - await button.focus(); - const handler = await button.asElement(); - await handler.click(); - await ensureMenuIsOpen(dbId, frame, retries); - return button; -} - -async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) { - await frame.waitFor(RETRY_DELAY); - const button = await frame.$(`div[data-test="${dbId}"]`); - const classList = await frame.evaluate((button) => { - return button.parentElement.classList; - }, button); - if (!Object.values(classList).includes("selected") && retries < 5) { - retries = retries + 1; - await clickDBMenu(dbId, frame, retries); - } -} diff --git a/test/mongo/mongoIndexPolicy.spec.ts b/test/mongo/mongoIndexPolicy.spec.ts deleted file mode 100644 index ce96a16ad..000000000 --- a/test/mongo/mongoIndexPolicy.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import "expect-puppeteer"; -import { createDatabase, generateUniqueName, onClickSaveButton } from "../utils/shared"; - -const LOADING_STATE_DELAY = 5000; -jest.setTimeout(300000); - -describe("MongoDB Index policy tests", () => { - it("Open, Create and Save Index", async () => { - try { - const singleFieldId = generateUniqueName("key"); - const wildCardId = generateUniqueName("key") + "$**"; - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner"); - const handle = await page.waitForSelector("iframe"); - const frame = await handle.contentFrame(); - const dropDown = "Index Type "; - let index = 0; - - //open dataBaseMenu - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - const { databaseId, collectionId } = await createDatabase(frame); - await frame.waitFor(25000); - // click on database - await frame.waitForSelector(`div[data-test="${databaseId}"]`); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.click(`div[data-test="${databaseId}"]`); - await frame.waitFor(LOADING_STATE_DELAY); - - // click on scale & setting - await frame.waitFor(`div[data-test="${collectionId}"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - await frame.click(`div[data-test="${collectionId}"]`); - - await frame.waitFor(`div[data-test="Scale & Settings"]`), { visible: true }; - await frame.waitFor(10000); - await frame.click(`div[data-test="Scale & Settings"]`); - - await frame.waitFor(`button[data-content="Indexing Policy"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - await frame.click(`button[data-content="Indexing Policy"]`); - - // Type to single Field - let throughput = await frame.$$(".ms-TextField-field"); - const selectedDropDownSingleField = dropDown + index; - await frame.waitFor(`div[aria-label="${selectedDropDownSingleField}"]`), { visible: true }; - await throughput[index].type(singleFieldId); - await frame.click(`div[aria-label="${selectedDropDownSingleField}"]`); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.click(`button[title="Single Field"]`); - index++; - - // Type to wild card - throughput = await frame.$$(".ms-TextField-field"); - await throughput[index].type(wildCardId); - const selectedDropDownWildCard = dropDown + index; - await frame.waitFor(`div[aria-label="${selectedDropDownWildCard}"]`), { visible: true }; - await frame.click(`div[aria-label="${selectedDropDownWildCard}"]`); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.click(`button[title="Wildcard"]`); - index++; - - // click save Button - await onClickSaveButton(frame); - - // check the array - let singleFieldIndexInserted = false, - wildCardIndexInserted = false; - await frame.waitFor("div[data-automationid='DetailsRowCell'] > span"), { visible: true }; - await frame.waitFor(20000); - - const elements = await frame.$$("div[data-automationid='DetailsRowCell'] > span"); - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - const text = await frame.evaluate((element) => element.textContent, element); - if (text.includes(wildCardId)) { - wildCardIndexInserted = true; - } else if (text.includes(singleFieldId)) { - singleFieldIndexInserted = true; - } - } - await frame.waitFor(20000); - expect(wildCardIndexInserted).toBe(true); - expect(singleFieldIndexInserted).toBe(true); - - //delete all index policy - await frame.waitFor("button[aria-label='Delete index Button']"), { visible: true }; - const deleteButton = await frame.$$("button[aria-label='Delete index Button']"); - for (let i = 0; i < deleteButton.length; i++) { - await frame.click(`button[aria-label="Delete index Button"]`); - } - await onClickSaveButton(frame); - - //check for cleaning - await frame.waitFor(20000); - await frame.waitFor("div[data-automationid='DetailsRowCell'] > span"), { visible: true }; - const isDeletionComplete = await frame.$$("div[data-automationid='DetailsRowCell'] > span"); - expect(isDeletionComplete).toHaveLength(2); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `failed-${testName}.jpg` }); - throw error; - } - }); -}); diff --git a/test/notebooks/testNotebooks/GettingStarted.ipynb b/test/notebooks/GettingStarted.ipynb similarity index 100% rename from test/notebooks/testNotebooks/GettingStarted.ipynb rename to test/notebooks/GettingStarted.ipynb diff --git a/test/notebooks/notebookTestUtils.ts b/test/notebooks/notebookTestUtils.ts deleted file mode 100644 index ddf4cf637..000000000 --- a/test/notebooks/notebookTestUtils.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ElementHandle, Frame } from "puppeteer"; -import * as path from "path"; - -export const NOTEBOOK_OPERATION_DELAY = 5000; -export const RENDER_DELAY = 2500; - -export const uploadNotebookIfNotExist = async (frame: Frame, notebookName: string): Promise> => { - const notebookNode = await getNotebookNode(frame, notebookName); - if (notebookNode) { - return notebookNode; - } - - const uploadNotebookPath = path.join(__dirname, "testNotebooks", notebookName); - const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree"); - const treeNodeHeadersBeforeUpload = await notebookResourceTree.$$(".treeNodeHeader"); - - const ellipses = await treeNodeHeadersBeforeUpload[2].$("button"); - await ellipses.click(); - - await frame.waitFor(RENDER_DELAY); - - const menuItems = await frame.$$(".ms-ContextualMenu-item"); - await menuItems[4].click(); - - const uploadFileButton = await frame.waitForSelector("#importFileButton"); - uploadFileButton.click(); - - const fileChooser = await page.waitForFileChooser(); - fileChooser.accept([uploadNotebookPath]); - - const submitButton = await frame.waitForSelector("#uploadFileButton"); - await submitButton.click(); - - await frame.waitFor(NOTEBOOK_OPERATION_DELAY); - return await getNotebookNode(frame, notebookName); -}; - -export const getNotebookNode = async (frame: Frame, uploadNotebookName: string): Promise> => { - const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree"); - await frame.waitFor(RENDER_DELAY); - let currentNotebookNode: ElementHandle; - - const treeNodeHeaders = await notebookResourceTree.$$(".treeNodeHeader"); - for (let i = 1; i < treeNodeHeaders.length; i++) { - currentNotebookNode = treeNodeHeaders[i]; - const nodeLabel = await currentNotebookNode.$eval(".nodeLabel", (element) => element.textContent); - if (nodeLabel === uploadNotebookName) { - return currentNotebookNode; - } - } - return undefined; -}; diff --git a/test/notebooks/upload.spec.ts b/test/notebooks/upload.spec.ts new file mode 100644 index 000000000..a0a4f779e --- /dev/null +++ b/test/notebooks/upload.spec.ts @@ -0,0 +1,27 @@ +import { jest } from "@jest/globals"; +import "expect-playwright"; +import fs from "fs"; +import path from "path"; +jest.setTimeout(240000); + +const filename = "GettingStarted.ipynb"; +const fileToUpload = `GettingStarted-ignore${Math.floor(Math.random() * 100000)}.ipynb`; + +fs.copyFileSync(path.join(__dirname, filename), path.join(__dirname, fileToUpload)); + +test("Notebooks", async () => { + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); + await page.waitForSelector("iframe"); + const explorer = page.frame({ + name: "explorer", + }); + // Upload and Delete Notebook + await explorer.click('[data-test="My Notebooks"] [aria-label="More"]'); + await explorer.click('button[role="menuitem"]:has-text("Upload File")'); + await explorer.setInputFiles("#importFileInput", path.join(__dirname, fileToUpload)); + await explorer.click('[aria-label="Submit"]'); + await explorer.click(`[data-test="${fileToUpload}"] [aria-label="More"]`); + await explorer.click('button[role="menuitem"]:has-text("Delete")'); + await explorer.click('button:has-text("Delete")'); + await expect(explorer).not.toHaveText(".notebookResourceTree", fileToUpload); +}); diff --git a/test/notebooks/uploadAndOpenNotebook.spec.ts b/test/notebooks/uploadAndOpenNotebook.spec.ts deleted file mode 100644 index ec96a0588..000000000 --- a/test/notebooks/uploadAndOpenNotebook.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { uploadNotebookIfNotExist } from "./notebookTestUtils"; - -jest.setTimeout(300000); - -const notebookName = "GettingStarted.ipynb"; - -describe("Notebook UI tests", () => { - it("Upload, Open and Delete Notebook", async () => { - try { - await page.goto("https://localhost:1234/testExplorer.html"); - const handle = await page.waitForSelector("iframe"); - const frame = await handle.contentFrame(); - await frame.waitForSelector(".galleryHeader"); - const uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName); - await uploadedNotebookNode.click(); - await frame.waitForSelector(".tabNavText"); - const tabTitle = await frame.$eval(".tabNavText", (element) => element.textContent); - expect(tabTitle).toEqual(notebookName); - const closeIcon = await frame.waitForSelector(".close-Icon"); - await closeIcon.click(); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `Test Failed ${testName}.jpg` }); - throw error; - } - }); -}); diff --git a/test/playwrightEnv.js b/test/playwrightEnv.js new file mode 100644 index 000000000..490022eeb --- /dev/null +++ b/test/playwrightEnv.js @@ -0,0 +1,26 @@ +const PlaywrightEnvironment = require("jest-playwright-preset/lib/PlaywrightEnvironment").default; + +class CustomEnvironment extends PlaywrightEnvironment { + async setup() { + await super.setup(); + // Your setup + } + + async teardown() { + // Your teardown + await super.teardown(); + } + + async handleTestEvent(event) { + if (event.name === "test_done" && event.test.errors.length > 0) { + const parentName = event.test.parent.name.replace(/\W/g, "-"); + const specName = event.test.name.replace(/\W/g, "-"); + + await this.global.page.screenshot({ + path: `screenshots/${parentName}_${specName}.png`, + }); + } + } +} + +module.exports = CustomEnvironment; diff --git a/test/selfServe/selfServeExample.spec.ts b/test/selfServe/selfServeExample.spec.ts index b2dec816e..7e10c1ce2 100644 --- a/test/selfServe/selfServeExample.spec.ts +++ b/test/selfServe/selfServeExample.spec.ts @@ -1,47 +1,36 @@ -jest.setTimeout(300000); +test("Self Serve", async () => { + await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html"); + const handle = await page.waitForSelector("iframe"); + const frame = await handle.contentFrame(); -describe("Self Serve", () => { - it("Launch Self Serve Example", async () => { - try { - await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html"); - const handle = await page.waitForSelector("iframe"); - const frame = await handle.contentFrame(); + // wait for refresh RP call to end + await page.waitForTimeout(10000); - // wait for refresh RP call to end - await frame.waitFor(10000); + // id of the display element is in the format {PROPERTY_NAME}-{DISPLAY_NAME}-{DISPLAY_TYPE} + await frame.waitForSelector("#description-text-display"); - // id of the display element is in the format {PROPERTY_NAME}-{DISPLAY_NAME}-{DISPLAY_TYPE} - await frame.waitForSelector("#description-text-display"); + const regions = await frame.waitForSelector("#regions-dropdown-input"); - const regions = await frame.waitForSelector("#regions-dropdown-input"); + const currentRegionsDescription = await frame.$$("#currentRegionText-text-display"); + expect(currentRegionsDescription).toHaveLength(0); + let disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]"); + expect(disabledLoggingToggle).toHaveLength(0); - const currentRegionsDescription = await frame.$$("#currentRegionText-text-display"); - expect(currentRegionsDescription).toHaveLength(0); - let disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]"); - expect(disabledLoggingToggle).toHaveLength(0); + await regions.click(); + const regionsDropdownElement1 = await frame.waitForSelector("#regions-dropdown-input-list0"); + await regionsDropdownElement1.click(); - await regions.click(); - const regionsDropdownElement1 = await frame.waitForSelector("#regions-dropdown-input-list0"); - await regionsDropdownElement1.click(); + await frame.waitForSelector("#currentRegionText-text-display"); + disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]"); + expect(disabledLoggingToggle).toHaveLength(1); - await frame.waitForSelector("#currentRegionText-text-display"); - disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]"); - expect(disabledLoggingToggle).toHaveLength(1); + await frame.waitForSelector("#accountName-textField-input"); - await frame.waitForSelector("#accountName-textField-input"); + const enableDbLevelThroughput = await frame.waitForSelector("#enableDbLevelThroughput-toggle-input"); + const dbThroughput = await frame.$$("#dbThroughput-slider-input"); + expect(dbThroughput).toHaveLength(0); + await enableDbLevelThroughput.click(); + await frame.waitForSelector("#dbThroughput-slider-input"); - const enableDbLevelThroughput = await frame.waitForSelector("#enableDbLevelThroughput-toggle-input"); - const dbThroughput = await frame.$$("#dbThroughput-slider-input"); - expect(dbThroughput).toHaveLength(0); - await enableDbLevelThroughput.click(); - await frame.waitForSelector("#dbThroughput-slider-input"); - - await frame.waitForSelector("#collectionThroughput-spinner-input"); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `Test Failed ${testName}.jpg` }); - throw error; - } - }); + await frame.waitForSelector("#collectionThroughput-spinner-input"); }); diff --git a/test/sql/container.spec.ts b/test/sql/container.spec.ts index ab3c3340b..689bd3196 100644 --- a/test/sql/container.spec.ts +++ b/test/sql/container.spec.ts @@ -1,163 +1,39 @@ -import "expect-puppeteer"; -import { Frame } from "puppeteer"; -import { generateDatabaseName, generateUniqueName } from "../utils/shared"; +import { jest } from "@jest/globals"; +import "expect-playwright"; +import { safeClick } from "../utils/safeClick"; +import { generateUniqueName } from "../utils/shared"; +jest.setTimeout(120000); -jest.setTimeout(300000); -const LOADING_STATE_DELAY = 2500; -const RETRY_DELAY = 5000; -const CREATE_DELAY = 10000; -const RENDER_DELAY = 1000; +test("SQL CRUD", async () => { + const databaseId = generateUniqueName("db"); + const containerId = generateUniqueName("container"); -describe("Collection Add and Delete SQL spec", () => { - it("creates a collection", async () => { - try { - const dbId = generateDatabaseName(); - const collectionId = generateUniqueName("col"); - const sharedKey = `/skey${generateUniqueName()}`; - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); - const handle = await page.waitForSelector("iframe"); - const frame = await handle.contentFrame(); - - // create new collection - await frame.waitFor('button[data-test="New Container"]', { visible: true }); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.click('button[data-test="New Container"]'); - - // check new database - await frame.waitFor('input[data-test="addCollection-createNewDatabase"]'); - await frame.click('input[data-test="addCollection-createNewDatabase"]'); - - // check shared throughput - await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]'); - await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]'); - - // type database id - await frame.waitFor('input[data-test="addCollection-newDatabaseId"]'); - const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]'); - await dbInput.press("Backspace"); - await dbInput.type(dbId); - - // type collection id - await frame.waitFor('input[data-test="addCollection-collectionId"]'); - const input = await frame.$('input[data-test="addCollection-collectionId"]'); - await input.press("Backspace"); - await input.type(collectionId); - - // type partition key value - await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]'); - const keyInput = await frame.$('input[data-test="addCollection-partitionKeyValue"]'); - await keyInput.press("Backspace"); - await keyInput.type(sharedKey); - - // click submit - await frame.waitFor("#submitBtnAddCollection"); - await frame.click("#submitBtnAddCollection"); - - // validate created - // open database menu - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(CREATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`); - const selectedDbId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, databases[0]); - - await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true }; - await frame.waitFor(CREATE_DELAY); - await frame.waitFor("div[class='rowData'] > span[class='message']"); - - await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - - await clickDBMenu(selectedDbId, frame); - - const collections = await frame.$$( - `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]` - ); - - if (collections.length) { - await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, { - visible: true, - }); - - const textId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, collections[0]); - await frame.waitFor(`div[data-test="${textId}"]`, { visible: true }); - // delete container - - // click context menu for container - await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true }); - await frame.click(`div[data-test="${textId}"] > div > button`); - - // click delete container - await frame.waitFor(RENDER_DELAY); - await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - - // confirm delete container - await frame.waitFor('input[id="confirmCollectionId"]', { visible: true }); - await frame.type('input[id="confirmCollectionId"]', textId); - - // click delete - await frame.waitFor("button.genericPaneSubmitBtn", { visible: true }); - await frame.click("button.genericPaneSubmitBtn"); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - await expect(page).not.toMatchElement(`div[data-test="${textId}"]`); - } - - // click context menu for database - await frame.waitFor(`div[data-test="${selectedDbId}"] > div > button`); - await frame.waitFor(CREATE_DELAY); - const button = await frame.$(`div[data-test="${selectedDbId}"] > div > button`); - await button.focus(); - await button.asElement().click(); - - // click delete database - await frame.waitFor(CREATE_DELAY); - await frame.waitFor('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]'); - await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]'); - - // confirm delete database - await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true }); - await frame.waitFor(CREATE_DELAY); - await frame.type('input[id="confirmDatabaseId"]', selectedDbId); - - // click delete - await frame.click('button[id="sidePanelOkButton"]'); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(CREATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await expect(page).not.toMatchElement(`div[data-test="${selectedDbId}"]`); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `failed-${testName}.jpg` }); - throw error; - } + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); + await page.waitForSelector("iframe"); + const explorer = page.frame({ + name: "explorer", }); + + await explorer.click('[data-test="New Container"]'); + await explorer.click('[data-test="addCollection-newDatabaseId"]'); + await explorer.fill('[data-test="addCollection-newDatabaseId"]', databaseId); + await explorer.click('[data-test="addCollection-collectionId"]'); + await explorer.fill('[data-test="addCollection-collectionId"]', containerId); + await explorer.click('[data-test="addCollection-collectionId"]'); + await explorer.fill('[data-test="addCollection-collectionId"]', containerId); + await explorer.click('[data-test="addCollection-partitionKeyValue"]'); + await explorer.fill('[data-test="addCollection-partitionKeyValue"]', "/pk"); + await explorer.click('[data-test="addCollection-createCollection"]'); + await safeClick(explorer, `.nodeItem >> text=${databaseId}`); + await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`); + await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Container")'); + await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId); + await explorer.click('[aria-label="Submit"]'); + await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`); + await explorer.click('button[role="menuitem"]:has-text("Delete Database")'); + await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); + await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId); + await explorer.click("#sidePanelOkButton"); + await expect(explorer).not.toHaveText(".dataResourceTree", databaseId); + await expect(explorer).not.toHaveText(".dataResourceTree", containerId); }); - -async function clickDBMenu(dbId: string, frame: Frame, retries = 0) { - const button = await frame.$(`div[data-test="${dbId}"]`); - await button.focus(); - const handler = await button.asElement(); - await handler.click(); - await ensureMenuIsOpen(dbId, frame, retries); - return button; -} - -async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) { - await frame.waitFor(RETRY_DELAY); - const button = await frame.$(`div[data-test="${dbId}"]`); - const classList = await frame.evaluate((button) => { - return button.parentElement.classList; - }, button); - if (!Object.values(classList).includes("selected") && retries < 5) { - retries = retries + 1; - await clickDBMenu(dbId, frame, retries); - } -} diff --git a/test/sql/resourceToken.spec.ts b/test/sql/resourceToken.spec.ts index 0ccf0c5ab..8f4d50073 100644 --- a/test/sql/resourceToken.spec.ts +++ b/test/sql/resourceToken.spec.ts @@ -1,83 +1,46 @@ -/* eslint-disable jest/expect-expect */ -import "expect-puppeteer"; -import { Frame } from "puppeteer"; -import { generateDatabaseName, generateUniqueName } from "../utils/shared"; -import { CosmosClient, PermissionMode } from "@azure/cosmos"; import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; +import { CosmosClient, PermissionMode } from "@azure/cosmos"; import * as msRestNodeAuth from "@azure/ms-rest-nodeauth"; +import { jest } from "@jest/globals"; +import "expect-playwright"; +import { generateDatabaseName, generateUniqueName } from "../utils/shared"; +jest.setTimeout(120000); -const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"]; +const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4"; const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"]; const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c"; const resourceGroupName = "runners"; -jest.setTimeout(300000); -const RETRY_DELAY = 5000; -const CREATE_DELAY = 10000; - -describe("Collection Add and Delete SQL spec", () => { - it("creates a collection", async () => { - const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); - const armClient = new CosmosDBManagementClient(credentials, subscriptionId); - const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner"); - const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner"); - const dbId = generateDatabaseName(); - const collectionId = generateUniqueName("col"); - const client = new CosmosClient({ - endpoint: account.documentEndpoint, - key: keys.primaryMasterKey, - }); - const { database } = await client.databases.createIfNotExists({ id: dbId }); - const { container } = await database.containers.createIfNotExists({ id: collectionId }); - const { user } = await database.users.upsert({ id: "testUser" }); - const { resource: containerPermission } = await user.permissions.upsert({ - id: "partitionLevelPermission", - permissionMode: PermissionMode.All, - resource: container.url, - }); - const resourceTokenConnectionString = `AccountEndpoint=${account.documentEndpoint};DatabaseId=${database.id};CollectionId=${container.id};${containerPermission._token}`; - try { - await page.goto(process.env.DATA_EXPLORER_ENDPOINT); - await page.waitFor("div > p.switchConnectTypeText", { visible: true }); - await page.click("div > p.switchConnectTypeText"); - await page.type("input[class='inputToken']", resourceTokenConnectionString); - await page.click("input[value='Connect']"); - const handle = await page.waitForSelector("iframe"); - const frame = await handle.contentFrame(); - // validate created - // open database menu - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(CREATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`); - expect(await frame.$(`span[title="${collectionId}"]`)).toBeDefined(); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `failed-${testName}.jpg` }); - throw error; - } +test("Resource token", async () => { + const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); + const armClient = new CosmosDBManagementClient(credentials, subscriptionId); + const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner"); + const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner"); + const dbId = generateDatabaseName(); + const collectionId = generateUniqueName("col"); + const client = new CosmosClient({ + endpoint: account.documentEndpoint, + key: keys.primaryMasterKey, }); + const { database } = await client.databases.createIfNotExists({ id: dbId }); + const { container } = await database.containers.createIfNotExists({ id: collectionId }); + const { user } = await database.users.upsert({ id: "testUser" }); + const { resource: containerPermission } = await user.permissions.upsert({ + id: "partitionLevelPermission", + permissionMode: PermissionMode.All, + resource: container.url, + }); + const resourceTokenConnectionString = `AccountEndpoint=${account.documentEndpoint};DatabaseId=${database.id};CollectionId=${container.id};${containerPermission._token}`; + + await page.goto("https://localhost:1234/hostedExplorer.html"); + await page.waitForSelector("div > p.switchConnectTypeText"); + await page.click("div > p.switchConnectTypeText"); + await page.type("input[class='inputToken']", resourceTokenConnectionString); + await page.click("input[value='Connect']"); + await page.waitForSelector("iframe"); + const explorer = page.frame({ + name: "explorer", + }); + await explorer.textContent(`css=.dataResourceTree >> "${collectionId}"`); }); - -async function clickDBMenu(dbId: string, frame: Frame, retries = 0) { - const button = await frame.$(`div[data-test="${dbId}"]`); - await button.focus(); - const handler = await button.asElement(); - await handler.click(); - await ensureMenuIsOpen(dbId, frame, retries); - return button; -} - -async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) { - await frame.waitFor(RETRY_DELAY); - const button = await frame.$(`div[data-test="${dbId}"]`); - const classList = await frame.evaluate((button) => { - return button.parentElement.classList; - }, button); - if (!Object.values(classList).includes("selected") && retries < 5) { - retries = retries + 1; - await clickDBMenu(dbId, frame, retries); - } -} diff --git a/test/tables/container.spec.ts b/test/tables/container.spec.ts index 0d1b7d79a..d679b89b6 100644 --- a/test/tables/container.spec.ts +++ b/test/tables/container.spec.ts @@ -1,111 +1,27 @@ -import "expect-puppeteer"; -import { Frame } from "puppeteer"; -import { generateUniqueName, login } from "../utils/shared"; +import { jest } from "@jest/globals"; +import "expect-playwright"; +import { safeClick } from "../utils/safeClick"; +import { generateUniqueName } from "../utils/shared"; -jest.setTimeout(300000); -const RETRY_DELAY = 5000; -const LOADING_STATE_DELAY = 2500; -const RENDER_DELAY = 1000; +jest.setTimeout(120000); -describe("Collection Add and Delete Tables spec", () => { - it("creates a collection", async () => { - try { - const tableId = generateUniqueName("tab"); - const frame = await login(process.env.TABLES_CONNECTION_STRING); +test("Tables CRUD", async () => { + const tableId = generateUniqueName("table"); - // create new collection - await frame.waitFor('button[data-test="New Table"]', { visible: true }); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.click('button[data-test="New Table"]'); - - // type database id - await frame.waitFor('input[data-test="addCollection-newDatabaseId"]'); - const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]'); - await dbInput.press("Backspace"); - await dbInput.type(tableId); - - // click submit - await frame.waitFor("#submitBtnAddCollection"); - await frame.click("#submitBtnAddCollection"); - - // validate created - // open database menu - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - await frame.waitFor(`div[data-test="TablesDB"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - - const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", (elements) => { - return elements.some((el) => el.textContent.includes("Successfully created")); - }); - - expect(didCreateContainer).toBe(true); - - await frame.waitFor(`div[data-test="TablesDB"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - - await clickTablesMenu(frame); - - const collections = await frame.$$( - `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]` - ); - const textId = await frame.evaluate((element) => { - return element.attributes["data-test"].textContent; - }, collections[0]); - await frame.waitFor(`div[data-test="${textId}"]`, { visible: true }); - - // delete container - - // click context menu for container - await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true }); - await frame.click(`div[data-test="${textId}"] > div > button`); - - // click delete container - await frame.waitFor(RENDER_DELAY); - await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]'); - - // confirm delete container - await frame.waitFor('input[id="confirmCollectionId"]', { visible: true }); - await frame.type('input[id="confirmCollectionId"]', textId); - - // click delete - await frame.waitFor("button.genericPaneSubmitBtn", { visible: true }); - await frame.click("button.genericPaneSubmitBtn"); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - await frame.waitFor(LOADING_STATE_DELAY); - await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); - - await expect(page).not.toMatchElement(`div[data-test="${textId}"]`); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testName = (expect as any).getState().currentTestName; - await page.screenshot({ path: `failed-${testName}.jpg` }); - throw error; - } + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner"); + await page.waitForSelector("iframe"); + const explorer = page.frame({ + name: "explorer", }); + + await explorer.click('[data-test="New Table"]'); + await explorer.click('[data-test="addCollection-collectionId"]'); + await explorer.fill('[data-test="addCollection-collectionId"]', tableId); + await explorer.click('[data-test="addCollection-createCollection"]'); + await safeClick(explorer, `[data-test="TablesDB"]`); + await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`); + await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")'); + await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId); + await explorer.click('[aria-label="Submit"]'); + await expect(explorer).not.toHaveText(".dataResourceTree", tableId); }); - -async function clickTablesMenu(frame: Frame, retries = 0) { - const button = await frame.$(`div[data-test="TablesDB"]`); - await button.focus(); - const handler = await button.asElement(); - await handler.click(); - await ensureMenuIsOpen(frame, retries); - return button; -} - -async function ensureMenuIsOpen(frame: Frame, retries: number) { - await frame.waitFor(RETRY_DELAY); - const button = await frame.$(`div[data-test="TablesDB"]`); - const classList = await frame.evaluate((button) => { - return button.parentElement.classList; - }, button); - if (!Object.values(classList).includes("selected") && retries < 5) { - retries = retries + 1; - await clickTablesMenu(frame, retries); - } -} diff --git a/test/utils/safeClick.ts b/test/utils/safeClick.ts new file mode 100644 index 000000000..d0c307bd0 --- /dev/null +++ b/test/utils/safeClick.ts @@ -0,0 +1,11 @@ +import { Frame } from "playwright"; + +export async function safeClick(page: Frame, selector: string): Promise { + // TODO: Remove. Playwright does this for you... mostly. + // But our knockout+react setup sometimes leaves dom nodes detached and even playwright can't recover. + // Resource tree is particually bad. + // Ideally this should only be added as a last resort + await page.waitForSelector(selector); + await page.waitForTimeout(5000); + await page.click(selector); +} diff --git a/test/utils/shared.ts b/test/utils/shared.ts index c7851f917..c8c992556 100644 --- a/test/utils/shared.ts +++ b/test/utils/shared.ts @@ -1,26 +1,4 @@ import crypto from "crypto"; -import { Frame } from "puppeteer"; - -const LOADING_STATE_DELAY = 3000; -const CREATE_DELAY = 10000; - -export async function login(connectionString: string): Promise { - const prodUrl = process.env.DATA_EXPLORER_ENDPOINT; - await page.goto(prodUrl); - - if (process.env.PLATFORM === "Emulator") { - return page.mainFrame(); - } - // log in with connection string - await page.waitFor("div > p.switchConnectTypeText", { visible: true }); - await page.click("div > p.switchConnectTypeText"); - const connStr = connectionString; - await page.type("input[class='inputToken']", connStr); - await page.click("input[value='Connect']"); - const handle = await page.waitForSelector("iframe"); - const frame = await handle.contentFrame(); - return frame; -} export function generateUniqueName(baseName = "", length = 4): string { return `${baseName}${crypto.randomBytes(length).toString("hex")}`; @@ -29,50 +7,3 @@ export function generateUniqueName(baseName = "", length = 4): string { export function generateDatabaseName(baseName = "db", length = 1): string { return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`; } - -export async function createDatabase(frame: Frame): Promise<{ databaseId: string; collectionId: string }> { - const databaseId = generateDatabaseName(); - const collectionId = generateUniqueName("col"); - const shardKey = "partitionKey"; - // create new collection - await frame.waitFor('button[data-test="New Collection"]', { visible: true }); - await frame.click('button[data-test="New Collection"]'); - - // check new database - await frame.waitFor('input[data-test="addCollection-createNewDatabase"]'); - await frame.click('input[data-test="addCollection-createNewDatabase"]'); - - // check shared throughput - await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]'); - await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]'); - - // type database id - await frame.waitFor('input[data-test="addCollection-newDatabaseId"]'); - const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]'); - await dbInput.press("Backspace"); - await dbInput.type(databaseId); - - // type collection id - await frame.waitFor('input[data-test="addCollection-collectionId"]'); - const input = await frame.$('input[data-test="addCollection-collectionId"]'); - await input.press("Backspace"); - await input.type(collectionId); - - // type partition key value - await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]'); - const keyInput = await frame.$('input[data-test="addCollection-partitionKeyValue"]'); - await keyInput.press("Backspace"); - await keyInput.type(shardKey); - - // click submit - await frame.waitFor("#submitBtnAddCollection"); - await frame.click("#submitBtnAddCollection"); - return { databaseId, collectionId }; -} - -export async function onClickSaveButton(frame: Frame): Promise { - await frame.waitFor(`button[data-test="Save"]`), { visible: true }; - await frame.waitFor(LOADING_STATE_DELAY); - await frame.click(`button[data-test="Save"]`); - await frame.waitFor(CREATE_DELAY); -} diff --git a/utils/accesibilityCheck.js b/utils/accesibilityCheck.js deleted file mode 100644 index 51f61875a..000000000 --- a/utils/accesibilityCheck.js +++ /dev/null @@ -1,22 +0,0 @@ -const { AxePuppeteer } = require("axe-puppeteer"); -const puppeteer = require("puppeteer"); - -(async () => { - const browser = await puppeteer.launch({ ignoreHTTPSErrors: true }); - const page = await browser.newPage(); - await page.setBypassCSP(true); - await page.goto("https://localhost:1234/hostedExplorer.html"); - - const results = await new AxePuppeteer(page).withTags(["wcag2a", "wcag2aa"]).analyze(); - if (results.violations && results.violations.length && results.violations.length > 0) { - throw results.violations; - } - - await page.close(); - await browser.close(); - console.log(`Accessibility Check Passed!`); -})().catch(err => { - console.error(`Accessibility Check Failed: ${err.length} Errors`); - console.error(err); - process.exit(1); -}); diff --git a/utils/cleanupDBs.js b/utils/cleanupDBs.js index ba01eb4ba..72fcfbafd 100644 --- a/utils/cleanupDBs.js +++ b/utils/cleanupDBs.js @@ -18,7 +18,6 @@ function friendlyTime(date) { } } -// Deletes all SQL and Mongo databases created more than 20 minutes ago in the test runner accounts async function main() { const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); const client = new CosmosDBManagementClient(credentials, subscriptionId); @@ -27,6 +26,7 @@ async function main() { if (account.kind === "MongoDB") { const mongoDatabases = await client.mongoDBResources.listMongoDBDatabases(resourceGroupName, account.name); for (const database of mongoDatabases) { + // Unfortunately Mongo does not provide a timestamp in ARM. There is no way to tell how old the DB is other thn encoding it in the ID :( const timestamp = Number(database.name.split("-")[1]); if (timestamp && timestamp < thirtyMinutesAgo) { await client.mongoDBResources.deleteMongoDBDatabase(resourceGroupName, account.name, database.name); @@ -35,10 +35,46 @@ async function main() { console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); } } + } else if (account.capabilities.find((c) => c.name === "EnableCassandra")) { + const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces( + resourceGroupName, + account.name + ); + for (const database of cassandraDatabases) { + const timestamp = Number(database.resource._ts) * 1000; + if (timestamp && timestamp < thirtyMinutesAgo) { + await client.cassandraResources.deleteCassandraKeyspace(resourceGroupName, account.name, database.name); + console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); + } else { + console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); + } + } + } else if (account.capabilities.find((c) => c.name === "EnableTable")) { + const tablesDatabase = await client.tableResources.listTables(resourceGroupName, account.name); + for (const database of tablesDatabase) { + const timestamp = Number(database.resource._ts) * 1000; + if (timestamp && timestamp < thirtyMinutesAgo) { + await client.tableResources.deleteTable(resourceGroupName, account.name, database.name); + console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); + } else { + console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); + } + } + } else if (account.capabilities.find((c) => c.name === "EnableGremlin")) { + const graphDatabases = await client.gremlinResources.listGremlinDatabases(resourceGroupName, account.name); + for (const database of graphDatabases) { + const timestamp = Number(database.resource._ts) * 1000; + if (timestamp && timestamp < thirtyMinutesAgo) { + await client.gremlinResources.deleteGremlinDatabase(resourceGroupName, account.name, database.name); + console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); + } else { + console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); + } + } } else if (account.kind === "GlobalDocumentDB") { const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name); for (const database of sqlDatabases) { - const timestamp = Number(database.name.split("-")[1]); + const timestamp = Number(database.resource._ts) * 1000; if (timestamp && timestamp < thirtyMinutesAgo) { await client.sqlResources.deleteSqlDatabase(resourceGroupName, account.name, database.name); console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); From b6d60dcc7b33830239756f1ca8f47b26faddf615 Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Tue, 20 Apr 2021 18:26:07 -0500 Subject: [PATCH 07/23] Remove unused @types/prop-types (#706) --- package-lock.json | 15 --------------- package.json | 1 - 2 files changed, 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99581af0c..28814aeb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6899,21 +6899,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, - "axe-core": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", - "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", - "dev": true - }, - "axe-puppeteer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-1.1.0.tgz", - "integrity": "sha512-VS17Y1rDQe6A0PdeTPxwOSBfmOdj6efgxyre9cN1du1snnVilczSDtQsgifBKBlzoL/3DKfGpgIi+N+zrzODOg==", - "dev": true, - "requires": { - "axe-core": "^3.5.3" - } - }, "axios": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", diff --git a/package.json b/package.json index e7706691e..dea1fc275 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,6 @@ "@types/memoize-one": "4.1.1", "@types/node": "12.11.1", "@types/promise.prototype.finally": "2.0.3", - "@types/prop-types": "15.5.8", "@types/q": "1.5.1", "@types/react": "17.0.3", "@types/react-dom": "17.0.3", From d58fececac730d8be9b65f6d174d2abf2aebc3ae Mon Sep 17 00:00:00 2001 From: vaidankarswapnil <81285216+vaidankarswapnil@users.noreply.github.com> Date: Wed, 21 Apr 2021 08:21:03 +0530 Subject: [PATCH 08/23] Move setup notebooks panel to react (#673) Co-authored-by: Steve Faulkner --- .eslintignore | 1 - src/Explorer/ComponentRegisterer.test.ts | 4 - src/Explorer/ComponentRegisterer.ts | 1 - .../SettingsComponent.test.tsx.snap | 112 -- src/Explorer/Explorer.tsx | 27 +- .../CommandBarComponentButtonFactory.tsx | 6 +- .../Panes/BrowseQueriesPanel/index.test.tsx | 2 +- src/Explorer/Panes/PaneComponents.ts | 10 - .../__snapshots__/index.test.tsx.snap | 56 - src/Explorer/Panes/SetupNotebooksPane.html | 45 - src/Explorer/Panes/SetupNotebooksPane.ts | 107 - .../SetupNotebooksPanel.test.tsx | 50 + .../SetupNotebooksPanel.tsx | 125 ++ .../SetupNotebooksPanel.test.tsx.snap | 1745 +++++++++++++++++ .../__snapshots__/index.test.tsx.snap | 28 - ...eteDatabaseConfirmationPanel.test.tsx.snap | 28 - src/Main.tsx | 1 - 17 files changed, 1939 insertions(+), 409 deletions(-) delete mode 100644 src/Explorer/Panes/SetupNotebooksPane.html delete mode 100644 src/Explorer/Panes/SetupNotebooksPane.ts create mode 100644 src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx create mode 100644 src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx create mode 100644 src/Explorer/Panes/SetupNotebooksPanel/__snapshots__/SetupNotebooksPanel.test.tsx.snap diff --git a/.eslintignore b/.eslintignore index 4e26bfc06..331c2a7e0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -126,7 +126,6 @@ src/Explorer/Panes/GraphStylingPane.ts src/Explorer/Panes/NewVertexPane.ts src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/RenewAdHocAccessPane.ts -src/Explorer/Panes/SetupNotebooksPane.ts src/Explorer/Panes/StringInputPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts src/Explorer/Panes/Tables/AddTableEntityPane.ts diff --git a/src/Explorer/ComponentRegisterer.test.ts b/src/Explorer/ComponentRegisterer.test.ts index 78e08fad1..626be3735 100644 --- a/src/Explorer/ComponentRegisterer.test.ts +++ b/src/Explorer/ComponentRegisterer.test.ts @@ -85,10 +85,6 @@ describe("Component Registerer", () => { expect(ko.components.isRegistered("string-input-pane")).toBe(true); }); - it("should register setup-notebooks-pane component", () => { - expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true); - }); - it("should register dynamic-list component", () => { expect(ko.components.isRegistered("dynamic-list")).toBe(true); }); diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index e3311cb3f..a39a2ad33 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -62,5 +62,4 @@ ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntit ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent()); -ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent()); ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent()); diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index db1ece0ab..af32c25a4 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -324,20 +324,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -722,20 +708,6 @@ exports[`SettingsComponent renders 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -1086,20 +1058,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -1484,20 +1442,6 @@ exports[`SettingsComponent renders 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -1861,20 +1805,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -2259,20 +2189,6 @@ exports[`SettingsComponent renders 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -2623,20 +2539,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -3021,20 +2923,6 @@ exports[`SettingsComponent renders 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 19a559458..a9d538df2 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -61,7 +61,7 @@ import { LoadQueryPanel } from "./Panes/LoadQueryPanel"; import NewVertexPane from "./Panes/NewVertexPane"; import { SaveQueryPanel } from "./Panes/SaveQueryPanel"; import { SettingsPane } from "./Panes/SettingsPane"; -import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane"; +import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { StringInputPane } from "./Panes/StringInputPane"; import AddTableEntityPane from "./Panes/Tables/AddTableEntityPane"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; @@ -182,7 +182,6 @@ export default class Explorer { public newVertexPane: NewVertexPane; public cassandraAddCollectionPane: CassandraAddCollectionPane; public stringInputPane: StringInputPane; - public setupNotebooksPane: SetupNotebooksPane; public gitHubReposPane: ContextualPaneBase; public publishNotebookPaneAdapter: ReactAdapter; public copyNotebookPaneAdapter: ReactAdapter; @@ -288,7 +287,6 @@ export default class Explorer { ((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) || userContext.features.enableNotebooks) ); - TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { isNotebookEnabled: this.isNotebookEnabled(), dataExplorerArea: Constants.Areas.Notebook, @@ -551,13 +549,6 @@ export default class Explorer { container: this, }); - this.setupNotebooksPane = new SetupNotebooksPane({ - id: "setupnotebookspane", - visible: ko.observable(false), - - container: this, - }); - this.tabsManager = params?.tabsManager ?? new TabsManager(); this.tabsManager.openedTabs.subscribe((tabs) => { if (tabs.length === 0) { @@ -575,7 +566,6 @@ export default class Explorer { this.newVertexPane, this.cassandraAddCollectionPane, this.stringInputPane, - this.setupNotebooksPane, ]; this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); this.isTabsContentExpanded = ko.observable(false); @@ -2133,7 +2123,7 @@ export default class Explorer { const description = "You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account."; - this.setupNotebooksPane.openWithTitleAndDescription(title, description); + this.openSetupNotebooksPanel(title, description); } public async handleOpenFileAction(path: string): Promise { @@ -2273,6 +2263,19 @@ export default class Explorer { ); } + public openSetupNotebooksPanel(title: string, description: string): void { + this.openSidePanel( + title, + this.expandConsole()} + panelTitle={title} + panelDescription={description} + /> + ); + } + public openTableSelectQueryPanel(queryViewModal: QueryViewModel): void { this.openSidePanel( "Select Column", diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 5bf8c5885..b59e0f526 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -445,7 +445,7 @@ function createEnableNotebooksButton(container: Explorer): CommandButtonComponen return { iconSrc: EnableNotebooksIcon, iconAlt: label, - onCommandClick: () => container.setupNotebooksPane.openWithTitleAndDescription(label, description), + onCommandClick: () => container.openSetupNotebooksPanel(label, description), commandButtonLabel: label, hasPopup: false, disabled: !container.isNotebooksEnabledForAccount(), @@ -482,7 +482,7 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon if (container.isNotebookEnabled()) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); } else { - container.setupNotebooksPane.openWithTitleAndDescription(title, description); + container.openSetupNotebooksPanel(title, description); } }, commandButtonLabel: label, @@ -508,7 +508,7 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo if (container.isNotebookEnabled()) { container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); } else { - container.setupNotebooksPane.openWithTitleAndDescription(title, description); + container.openSetupNotebooksPanel(title, description); } }, commandButtonLabel: label, diff --git a/src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx b/src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx index 6bab81037..195aac75c 100644 --- a/src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx +++ b/src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx @@ -10,7 +10,7 @@ describe("Browse queries panel", () => { const fakeExplorer = {} as Explorer; fakeExplorer.canSaveQueries = ko.computed(() => true); const fakeClientQuery = {} as QueriesClient; - const fakeQueryData = {} as Query[]; + const fakeQueryData = [] as Query[]; fakeClientQuery.getQueries = async () => fakeQueryData; fakeExplorer.queriesClient = fakeClientQuery; const props = { diff --git a/src/Explorer/Panes/PaneComponents.ts b/src/Explorer/Panes/PaneComponents.ts index 107bc46dd..d9da4a530 100644 --- a/src/Explorer/Panes/PaneComponents.ts +++ b/src/Explorer/Panes/PaneComponents.ts @@ -4,7 +4,6 @@ import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.htm import GitHubReposPaneTemplate from "./GitHubReposPane.html"; import GraphNewVertexPaneTemplate from "./GraphNewVertexPane.html"; import GraphStylingPaneTemplate from "./GraphStylingPane.html"; -import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html"; import StringInputPaneTemplate from "./StringInputPane.html"; import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html"; import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html"; @@ -86,15 +85,6 @@ export class StringInputPaneComponent { } } -export class SetupNotebooksPaneComponent { - constructor() { - return { - viewModel: PaneComponent, - template: SetupNotebooksPaneTemplate, - }; - } -} - export class GitHubReposPaneComponent { constructor() { return { diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap index 0cf982b10..31fb1aa7c 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap @@ -300,20 +300,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -698,20 +684,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -1185,20 +1157,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -1583,20 +1541,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { diff --git a/src/Explorer/Panes/SetupNotebooksPane.html b/src/Explorer/Panes/SetupNotebooksPane.html deleted file mode 100644 index 20fbc53b2..000000000 --- a/src/Explorer/Panes/SetupNotebooksPane.html +++ /dev/null @@ -1,45 +0,0 @@ -
    -
    -
    - -
    -
    - -
    - -
    - Close -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    - - -
    - loading indicator image -
    - -
    -
    diff --git a/src/Explorer/Panes/SetupNotebooksPane.ts b/src/Explorer/Panes/SetupNotebooksPane.ts deleted file mode 100644 index d19fe6e52..000000000 --- a/src/Explorer/Panes/SetupNotebooksPane.ts +++ /dev/null @@ -1,107 +0,0 @@ -import * as ViewModels from "../../Contracts/ViewModels"; -import { Action } from "../../Shared/Telemetry/TelemetryConstants"; -import { Areas, KeyCodes } from "../../Common/Constants"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; -import { ContextualPaneBase } from "./ContextualPaneBase"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import * as ko from "knockout"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; - -export class SetupNotebooksPane extends ContextualPaneBase { - private description: ko.Observable; - - constructor(options: ViewModels.PaneOptions) { - super(options); - - this.description = ko.observable(); - this.resetData(); - } - - public openWithTitleAndDescription(title: string, description: string) { - this.title(title); - this.description(description); - - this.open(); - } - - public open() { - super.open(); - const completeSetupBtn = document.getElementById("completeSetupBtn"); - completeSetupBtn && completeSetupBtn.focus(); - } - - public submit() { - // override default behavior because this is not a form - } - - public onCompleteSetupClick = async (src: any, event: MouseEvent) => { - await this.setupNotebookWorkspace(); - }; - - public onCompleteSetupKeyPress = async (src: any, event: KeyboardEvent) => { - if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) { - await this.setupNotebookWorkspace(); - event.stopPropagation(); - return false; - } - return true; - }; - - public async setupNotebookWorkspace(): Promise { - if (!this.container) { - return; - } - - const startKey: number = TelemetryProcessor.traceStart(Action.CreateNotebookWorkspace, { - dataExplorerArea: Areas.ContextualPane, - paneTitle: this.title(), - }); - const id = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - "Creating a new default notebook workspace" - ); - try { - this.isExecuting(true); - await this.container.notebookWorkspaceManager.createNotebookWorkspaceAsync( - this.container.databaseAccount() && this.container.databaseAccount().id, - "default" - ); - this.container.isAccountReady.valueHasMutated(); // re-trigger init notebooks - this.close(); - TelemetryProcessor.traceSuccess( - Action.CreateNotebookWorkspace, - { - dataExplorerArea: Areas.ContextualPane, - paneTitle: this.title(), - }, - startKey - ); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - "Successfully created a default notebook workspace for the account" - ); - } catch (error) { - const errorMessage = getErrorMessage(error); - TelemetryProcessor.traceFailure( - Action.CreateNotebookWorkspace, - { - dataExplorerArea: Areas.ContextualPane, - paneTitle: this.title(), - error: errorMessage, - errorStack: getErrorStack(error), - }, - startKey - ); - this.formErrors("Failed to setup a default notebook workspace"); - this.formErrorsDetails(`Failed to setup a default notebook workspace: ${errorMessage}`); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Failed to create a default notebook workspace: ${errorMessage}` - ); - } finally { - this.isExecuting(false); - NotificationConsoleUtils.clearInProgressMessageWithId(id); - } - } -} diff --git a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx new file mode 100644 index 000000000..63e76a38e --- /dev/null +++ b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx @@ -0,0 +1,50 @@ +import { mount } from "enzyme"; +import { PrimaryButton } from "office-ui-fabric-react"; +import React from "react"; +import Explorer from "../../Explorer"; +import { SetupNoteBooksPanel } from "./SetupNotebooksPanel"; + +describe("Setup Notebooks Panel", () => { + it("should render Default properly", () => { + const fakeExplorer = {} as Explorer; + const props = { + explorer: fakeExplorer, + closePanel: (): void => undefined, + openNotificationConsole: (): void => undefined, + panelTitle: "", + panelDescription: "", + }; + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + }); + + it("should render button", () => { + const fakeExplorer = {} as Explorer; + const props = { + explorer: fakeExplorer, + closePanel: (): void => undefined, + openNotificationConsole: (): void => undefined, + panelTitle: "", + panelDescription: "", + }; + const wrapper = mount(); + const button = wrapper.find("PrimaryButton").first(); + expect(button).toBeDefined(); + }); + + it("Button onClick should call onCompleteSetup", () => { + const onCompleteSetupClick = jest.fn(); + const wrapper = mount(); + wrapper.find("button").simulate("click"); + + expect(onCompleteSetupClick).toHaveBeenCalled(); + }); + + it("Button onKeyPress should call onCompleteSetupKeyPress", () => { + const onCompleteSetupKeyPress = jest.fn(); + const wrapper = mount(); + wrapper.find("button").simulate("keypress"); + + expect(onCompleteSetupKeyPress).toHaveBeenCalled(); + }); +}); diff --git a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx new file mode 100644 index 000000000..48a98a2d3 --- /dev/null +++ b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx @@ -0,0 +1,125 @@ +import { useBoolean } from "@uifabric/react-hooks"; +import { PrimaryButton } from "office-ui-fabric-react"; +import React, { FunctionComponent, KeyboardEvent, useState } from "react"; +import { Areas, NormalizedEventKey } from "../../../Common/Constants"; +import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; +import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; +import { userContext } from "../../../UserContext"; +import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; +import Explorer from "../../Explorer"; +import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; +import { PanelLoadingScreen } from "../PanelLoadingScreen"; +interface SetupNoteBooksPanelProps { + explorer: Explorer; + closePanel: () => void; + openNotificationConsole: () => void; + panelTitle: string; + panelDescription: string; +} + +export const SetupNoteBooksPanel: FunctionComponent = ({ + explorer, + closePanel, + openNotificationConsole, + panelTitle, + panelDescription, +}: SetupNoteBooksPanelProps): JSX.Element => { + const title = panelTitle; + const description = panelDescription; + const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false); + const [errorMessage, setErrorMessage] = useState(""); + const [showErrorDetails, setShowErrorDetails] = useState(false); + + const onCompleteSetupClick = async () => { + await setupNotebookWorkspace(); + }; + + const onCompleteSetupKeyPress = async (event: KeyboardEvent) => { + if (event.key === " " || event.key === NormalizedEventKey.Enter) { + await setupNotebookWorkspace(); + event.stopPropagation(); + return false; + } + return true; + }; + + const setupNotebookWorkspace = async (): Promise => { + if (!explorer) { + return; + } + + const startKey: number = TelemetryProcessor.traceStart(Action.CreateNotebookWorkspace, { + dataExplorerArea: Areas.ContextualPane, + paneTitle: title, + }); + + const clear = NotificationConsoleUtils.logConsoleProgress("Creating a new default notebook workspace"); + + try { + setLoadingTrue(); + await explorer.notebookWorkspaceManager.createNotebookWorkspaceAsync( + userContext.databaseAccount && userContext.databaseAccount.id, + "default" + ); + explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks + + closePanel(); + + TelemetryProcessor.traceSuccess( + Action.CreateNotebookWorkspace, + { + dataExplorerArea: Areas.ContextualPane, + paneTitle: title, + }, + startKey + ); + NotificationConsoleUtils.logConsoleInfo("Successfully created a default notebook workspace for the account"); + } catch (error) { + const errorMessage = getErrorMessage(error); + TelemetryProcessor.traceFailure( + Action.CreateNotebookWorkspace, + { + dataExplorerArea: Areas.ContextualPane, + paneTitle: title, + error: errorMessage, + errorStack: getErrorStack(error), + }, + startKey + ); + setErrorMessage(`Failed to setup a default notebook workspace: ${errorMessage}`); + setShowErrorDetails(true); + NotificationConsoleUtils.logConsoleError(`Failed to create a default notebook workspace: ${errorMessage}`); + } finally { + setLoadingFalse(); + clear(); + } + }; + + return ( +
    + {errorMessage && ( + + )} +
    +
    +
    {description}
    + +
    +
    + {isLoading && } + + ); +}; diff --git a/src/Explorer/Panes/SetupNotebooksPanel/__snapshots__/SetupNotebooksPanel.test.tsx.snap b/src/Explorer/Panes/SetupNotebooksPanel/__snapshots__/SetupNotebooksPanel.test.tsx.snap new file mode 100644 index 000000000..356024d46 --- /dev/null +++ b/src/Explorer/Panes/SetupNotebooksPanel/__snapshots__/SetupNotebooksPanel.test.tsx.snap @@ -0,0 +1,1745 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Setup Notebooks Panel should render Default properly 1`] = ` + +
    +
    +
    +
    + + + + + *": Object { + "left": 0, + "position": "relative", + "top": 0, + }, + }, + "textAlign": "center", + "textDecoration": "none", + "userSelect": "none", + }, + Object { + "height": "32px", + "minWidth": "80px", + }, + Object { + "backgroundColor": "#0078d4", + "border": "1px solid #0078d4", + "color": "#ffffff", + "selectors": Object { + ".ms-Fabric--isFocusVisible &:focus": Object { + "selectors": Object { + ":after": Object { + "border": "none", + "outlineColor": "#ffffff", + }, + }, + }, + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "borderColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + ], + "rootChecked": Object { + "backgroundColor": "#005a9e", + "color": "#ffffff", + }, + "rootCheckedHovered": Object { + "backgroundColor": "#005a9e", + "color": "#ffffff", + }, + "rootDisabled": Array [ + Object { + "outline": "transparent", + "position": "relative", + "selectors": Object { + ".ms-Fabric--isFocusVisible &:focus:after": Object { + "border": "1px solid transparent", + "bottom": 2, + "content": "\\"\\"", + "left": 2, + "outline": "1px solid #605e5c", + "position": "absolute", + "right": 2, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "bottom": -2, + "left": -2, + "outlineColor": "ButtonText", + "right": -2, + "top": -2, + }, + }, + "top": 2, + "zIndex": 1, + }, + "::-moz-focus-inner": Object { + "border": "0", + }, + }, + }, + Object { + "backgroundColor": "#f3f2f1", + "borderColor": "#f3f2f1", + "color": "#a19f9d", + "cursor": "default", + "pointerEvents": "none", + "selectors": Object { + ":focus": Object { + "outline": 0, + }, + ":hover": Object { + "outline": 0, + }, + }, + }, + Object { + "backgroundColor": "#f3f2f1", + "color": "#d2d0ce", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + }, + }, + }, + ], + "rootExpanded": Object { + "backgroundColor": "#005a9e", + "color": "#ffffff", + }, + "rootHovered": Object { + "backgroundColor": "#106ebe", + "border": "1px solid #106ebe", + "color": "#ffffff", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Highlight", + "borderColor": "Highlight", + "color": "Window", + }, + }, + }, + "rootPressed": Object { + "backgroundColor": "#005a9e", + "border": "1px solid #005a9e", + "color": "#ffffff", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "borderColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + "screenReaderText": Object { + "border": 0, + "height": 1, + "margin": -1, + "overflow": "hidden", + "padding": 0, + "position": "absolute", + "width": 1, + }, + "splitButtonContainer": Array [ + Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "border": "none", + }, + }, + }, + Object { + "outline": "transparent", + "position": "relative", + "selectors": Object { + ".ms-Fabric--isFocusVisible &:focus:after": Object { + "border": "1px solid #ffffff", + "bottom": 3, + "content": "\\"\\"", + "left": 3, + "outline": "1px solid #605e5c", + "position": "absolute", + "right": 3, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "border": "none", + "bottom": -2, + "left": -2, + "right": -2, + "top": -2, + }, + }, + "top": 3, + "zIndex": 1, + }, + "::-moz-focus-inner": Object { + "border": "0", + }, + }, + }, + Object { + "display": "inline-flex", + "selectors": Object { + ".ms-Button--default": Object { + "borderBottomRightRadius": "0", + "borderRight": "none", + "borderTopRightRadius": "0", + }, + ".ms-Button--primary": Object { + "border": "none", + "borderBottomRightRadius": "0", + "borderTopRightRadius": "0", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "Window", + "border": "1px solid WindowText", + "borderRightWidth": "0", + "color": "WindowText", + "forcedColorAdjust": "none", + }, + }, + }, + ".ms-Button--primary + .ms-Button": Object { + "border": "none", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "border": "1px solid WindowText", + "borderLeftWidth": "0", + }, + }, + }, + }, + }, + ], + "splitButtonContainerChecked": Object { + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + }, + }, + "splitButtonContainerCheckedHovered": Object { + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + }, + }, + "splitButtonContainerDisabled": Object { + "border": "none", + "outline": "none", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + "forcedColorAdjust": "none", + }, + }, + }, + "splitButtonContainerFocused": Object { + "outline": "none!important", + }, + "splitButtonContainerHovered": Object { + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Highlight", + "color": "Window", + }, + }, + }, + ".ms-Button.is-disabled": Object { + "color": "#a19f9d", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + }, + }, + }, + }, + }, + "splitButtonDivider": Array [ + Object { + "backgroundColor": "#ffffff", + "bottom": 8, + "position": "absolute", + "right": 31, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + }, + }, + "top": 8, + "width": 1, + }, + Object { + "bottom": 8, + "position": "absolute", + "right": 31, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "WindowText", + }, + }, + "top": 8, + "width": 1, + }, + ], + "splitButtonDividerDisabled": Object { + "bottom": 8, + "position": "absolute", + "right": 31, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "GrayText", + }, + }, + "top": 8, + "width": 1, + }, + "splitButtonFlexContainer": Object { + "alignItems": "center", + "display": "flex", + "flexWrap": "nowrap", + "height": "100%", + "justifyContent": "center", + }, + "splitButtonMenuButton": Array [ + Object { + "backgroundColor": "#0078d4", + "color": "#ffffff", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#106ebe", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "color": "Highlight", + }, + }, + }, + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "WindowText", + }, + }, + }, + Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + ".ms-Button-menuIcon": Object { + "color": "WindowText", + }, + }, + "border": "1px solid #8a8886", + "borderBottomRightRadius": "2px", + "borderLeft": "none", + "borderRadius": 0, + "borderTopRightRadius": "2px", + "boxSizing": "border-box", + "cursor": "pointer", + "display": "inline-block", + "height": "auto", + "marginBottom": 0, + "marginLeft": -1, + "marginRight": 0, + "marginTop": 0, + "outline": "transparent", + "padding": 6, + "textAlign": "center", + "textDecoration": "none", + "userSelect": "none", + "verticalAlign": "top", + "width": 32, + }, + ], + "splitButtonMenuButtonChecked": Object { + "backgroundColor": "#005a9e", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#005a9e", + }, + }, + }, + "splitButtonMenuButtonDisabled": Array [ + Object { + "backgroundColor": "#f3f2f1", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#f3f2f1", + }, + }, + }, + Object { + "border": "none", + "pointerEvents": "none", + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + }, + }, + }, + ".ms-Button-menuIcon": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "color": "GrayText", + }, + }, + }, + ":hover": Object { + "cursor": "default", + }, + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "border": "1px solid GrayText", + "color": "GrayText", + }, + }, + }, + ], + "splitButtonMenuButtonExpanded": Object { + "backgroundColor": "#005a9e", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#005a9e", + }, + }, + }, + "splitButtonMenuIcon": Object { + "color": "#ffffff", + }, + "splitButtonMenuIconDisabled": Object { + "color": "#a19f9d", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "color": "GrayText", + }, + }, + }, + "textContainer": Object { + "display": "block", + "flexGrow": 1, + }, + } + } + text="Complete Setup" + theme={ + Object { + "disableGlobalClassNames": false, + "effects": Object { + "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", + "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", + "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", + "elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)", + "roundedCorner2": "2px", + "roundedCorner4": "4px", + "roundedCorner6": "6px", + }, + "fonts": Object { + "large": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "18px", + "fontWeight": 400, + }, + "medium": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "14px", + "fontWeight": 400, + }, + "mediumPlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "16px", + "fontWeight": 400, + }, + "mega": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "68px", + "fontWeight": 600, + }, + "small": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "12px", + "fontWeight": 400, + }, + "smallPlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "12px", + "fontWeight": 400, + }, + "superLarge": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "42px", + "fontWeight": 600, + }, + "tiny": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "10px", + "fontWeight": 400, + }, + "xLarge": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "20px", + "fontWeight": 600, + }, + "xLargePlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "24px", + "fontWeight": 600, + }, + "xSmall": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "10px", + "fontWeight": 400, + }, + "xxLarge": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "28px", + "fontWeight": 600, + }, + "xxLargePlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "32px", + "fontWeight": 600, + }, + }, + "isInverted": false, + "palette": Object { + "accent": "#0078d4", + "black": "#000000", + "blackTranslucent40": "rgba(0,0,0,.4)", + "blue": "#0078d4", + "blueDark": "#002050", + "blueLight": "#00bcf2", + "blueMid": "#00188f", + "green": "#107c10", + "greenDark": "#004b1c", + "greenLight": "#bad80a", + "magenta": "#b4009e", + "magentaDark": "#5c005c", + "magentaLight": "#e3008c", + "neutralDark": "#201f1e", + "neutralLight": "#edebe9", + "neutralLighter": "#f3f2f1", + "neutralLighterAlt": "#faf9f8", + "neutralPrimary": "#323130", + "neutralPrimaryAlt": "#3b3a39", + "neutralQuaternary": "#d2d0ce", + "neutralQuaternaryAlt": "#e1dfdd", + "neutralSecondary": "#605e5c", + "neutralSecondaryAlt": "#8a8886", + "neutralTertiary": "#a19f9d", + "neutralTertiaryAlt": "#c8c6c4", + "orange": "#d83b01", + "orangeLight": "#ea4300", + "orangeLighter": "#ff8c00", + "purple": "#5c2d91", + "purpleDark": "#32145a", + "purpleLight": "#b4a0ff", + "red": "#e81123", + "redDark": "#a4262c", + "teal": "#008272", + "tealDark": "#004b50", + "tealLight": "#00b294", + "themeDark": "#005a9e", + "themeDarkAlt": "#106ebe", + "themeDarker": "#004578", + "themeLight": "#c7e0f4", + "themeLighter": "#deecf9", + "themeLighterAlt": "#eff6fc", + "themePrimary": "#0078d4", + "themeSecondary": "#2b88d8", + "themeTertiary": "#71afe5", + "white": "#ffffff", + "whiteTranslucent40": "rgba(255,255,255,.4)", + "yellow": "#ffb900", + "yellowDark": "#d29200", + "yellowLight": "#fff100", + }, + "rtl": undefined, + "semanticColors": Object { + "accentButtonBackground": "#0078d4", + "accentButtonText": "#ffffff", + "actionLink": "#323130", + "actionLinkHovered": "#201f1e", + "blockingBackground": "#FDE7E9", + "blockingIcon": "#FDE7E9", + "bodyBackground": "#ffffff", + "bodyBackgroundChecked": "#edebe9", + "bodyBackgroundHovered": "#f3f2f1", + "bodyDivider": "#edebe9", + "bodyFrameBackground": "#ffffff", + "bodyFrameDivider": "#edebe9", + "bodyStandoutBackground": "#faf9f8", + "bodySubtext": "#605e5c", + "bodyText": "#323130", + "bodyTextChecked": "#000000", + "buttonBackground": "#ffffff", + "buttonBackgroundChecked": "#c8c6c4", + "buttonBackgroundCheckedHovered": "#edebe9", + "buttonBackgroundDisabled": "#f3f2f1", + "buttonBackgroundHovered": "#f3f2f1", + "buttonBackgroundPressed": "#edebe9", + "buttonBorder": "#8a8886", + "buttonBorderDisabled": "#f3f2f1", + "buttonText": "#323130", + "buttonTextChecked": "#201f1e", + "buttonTextCheckedHovered": "#000000", + "buttonTextDisabled": "#a19f9d", + "buttonTextHovered": "#201f1e", + "buttonTextPressed": "#201f1e", + "cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", + "cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)", + "cardStandoutBackground": "#ffffff", + "defaultStateBackground": "#faf9f8", + "disabledBackground": "#f3f2f1", + "disabledBodySubtext": "#c8c6c4", + "disabledBodyText": "#a19f9d", + "disabledBorder": "#c8c6c4", + "disabledSubtext": "#d2d0ce", + "disabledText": "#a19f9d", + "errorBackground": "#FDE7E9", + "errorIcon": "#A80000", + "errorText": "#a4262c", + "focusBorder": "#605e5c", + "infoBackground": "#f3f2f1", + "infoIcon": "#605e5c", + "inputBackground": "#ffffff", + "inputBackgroundChecked": "#0078d4", + "inputBackgroundCheckedHovered": "#005a9e", + "inputBorder": "#605e5c", + "inputBorderHovered": "#323130", + "inputFocusBorderAlt": "#0078d4", + "inputForegroundChecked": "#ffffff", + "inputIcon": "#0078d4", + "inputIconDisabled": "#a19f9d", + "inputIconHovered": "#005a9e", + "inputPlaceholderBackgroundChecked": "#deecf9", + "inputPlaceholderText": "#605e5c", + "inputText": "#323130", + "inputTextHovered": "#201f1e", + "link": "#0078d4", + "linkHovered": "#004578", + "listBackground": "#ffffff", + "listHeaderBackgroundHovered": "#f3f2f1", + "listHeaderBackgroundPressed": "#edebe9", + "listItemBackgroundChecked": "#edebe9", + "listItemBackgroundCheckedHovered": "#e1dfdd", + "listItemBackgroundHovered": "#f3f2f1", + "listText": "#323130", + "listTextColor": "#323130", + "menuBackground": "#ffffff", + "menuDivider": "#c8c6c4", + "menuHeader": "#0078d4", + "menuIcon": "#0078d4", + "menuItemBackgroundChecked": "#edebe9", + "menuItemBackgroundHovered": "#f3f2f1", + "menuItemBackgroundPressed": "#edebe9", + "menuItemText": "#323130", + "menuItemTextHovered": "#201f1e", + "messageLink": "#005A9E", + "messageLinkHovered": "#004578", + "messageText": "#323130", + "primaryButtonBackground": "#0078d4", + "primaryButtonBackgroundDisabled": "#f3f2f1", + "primaryButtonBackgroundHovered": "#106ebe", + "primaryButtonBackgroundPressed": "#005a9e", + "primaryButtonBorder": "transparent", + "primaryButtonText": "#ffffff", + "primaryButtonTextDisabled": "#d2d0ce", + "primaryButtonTextHovered": "#ffffff", + "primaryButtonTextPressed": "#ffffff", + "severeWarningBackground": "#FED9CC", + "severeWarningIcon": "#D83B01", + "smallInputBorder": "#605e5c", + "successBackground": "#DFF6DD", + "successIcon": "#107C10", + "successText": "#107C10", + "variantBorder": "#edebe9", + "variantBorderHovered": "#a19f9d", + "warningBackground": "#FFF4CE", + "warningHighlight": "#ffb900", + "warningIcon": "#797775", + "warningText": "#323130", + }, + "spacing": Object { + "l1": "20px", + "l2": "32px", + "m": "16px", + "s1": "8px", + "s2": "4px", + }, + } + } + variantClassName="ms-Button--primary" + > + + + + + + + +
    +
    + + +`; diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap index 64fee8529..1863eb33d 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap @@ -300,20 +300,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -698,20 +684,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 69fa8c792..08c7d2d8c 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -301,20 +301,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "title": [Function], "visible": [Function], }, - SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], @@ -703,20 +689,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, "setNotificationConsoleData": undefined, - "setupNotebooksPane": SetupNotebooksPane { - "container": [Circular], - "description": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "setupnotebookspane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onCompleteSetupClick": [Function], - "onCompleteSetupKeyPress": [Function], - "title": [Function], - "visible": [Function], - }, "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { diff --git a/src/Main.tsx b/src/Main.tsx index 5f0c0bcc8..ae9c400b7 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -238,7 +238,6 @@ const App: React.FunctionComponent = () => {
    -
    From a07aff1e8c7778b1eb99c0ef3642c5a2f18aa2a0 Mon Sep 17 00:00:00 2001 From: victor-meng <56978073+victor-meng@users.noreply.github.com> Date: Wed, 21 Apr 2021 08:11:33 -0700 Subject: [PATCH 09/23] resetData should not set isAutoPilotSelected flag (#708) --- src/Explorer/Panes/AddCollectionPane.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index 1cddb72b1..e20786d55 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -917,8 +917,10 @@ export default class AddCollectionPane extends ContextualPaneBase { this.databaseId(""); this.partitionKey(""); this.throughputSpendAck(false); - this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); + if (!this.container.isServerlessEnabled()) { + this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); + this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); + } this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); From d5f3230f6f96ab536178842e6ff5f8be05c466f7 Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Wed, 21 Apr 2021 11:01:16 -0500 Subject: [PATCH 10/23] Retry flaky tests --- .github/workflows/ci.yml | 10 +++++++++- jest-playwright.config.js | 4 ---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 574a8d613..e0a0f8614 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,7 +154,15 @@ jobs: - run: npm start & - run: npm run wait-for-server - name: ${{ matrix['test-file'] }} - run: npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} + run: | + # Run tests up to three times + n=0 + until [ "$n" -ge 3 ] + do + npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && break + n=$((n+1)) + sleep 1 + done shell: bash - uses: actions/upload-artifact@v2 if: failure() diff --git a/jest-playwright.config.js b/jest-playwright.config.js index 53103b830..d66e8f221 100644 --- a/jest-playwright.config.js +++ b/jest-playwright.config.js @@ -9,9 +9,5 @@ module.exports = { }, contextOptions: { ignoreHTTPSErrors: true, - viewport: { - width: 1920, - height: 1080, - }, }, }; From 72ce5fc8139b5146098651097be0a5e9fbf5318e Mon Sep 17 00:00:00 2001 From: Sunil Kumar Yadav <79906609+sunilyadav840@users.noreply.github.com> Date: Wed, 21 Apr 2021 22:03:29 +0530 Subject: [PATCH 11/23] Migrate Add Table Entity Pane to React (#642) Co-authored-by: Steve Faulkner --- .eslintignore | 1 - src/Common/EntityValue.tsx | 61 + src/Common/TableEntity.tsx | 136 + .../SettingsComponent.test.tsx.snap | 288 -- src/Explorer/Explorer.tsx | 27 +- src/Explorer/Panes/PanelComponent.less | 15 + .../Panes/PanelContainerComponent.tsx | 7 +- .../__snapshots__/index.test.tsx.snap | 144 - .../Panes/Tables/AddTableEntityPane.ts | 150 - .../Panes/Tables/AddTableEntityPanel.test.tsx | 49 + .../Panes/Tables/AddTableEntityPanel.tsx | 324 ++ .../Tables/Validators/EntityTableHelper.tsx | 247 ++ .../AddTableEntityPanel.test.tsx.snap | 3490 +++++++++++++++++ .../__snapshots__/index.test.tsx.snap | 72 - ...eteDatabaseConfirmationPanel.test.tsx.snap | 72 - src/Explorer/Tabs/QueryTablesTab.ts | 3 +- src/Main.tsx | 1 - 17 files changed, 4343 insertions(+), 744 deletions(-) create mode 100644 src/Common/EntityValue.tsx create mode 100644 src/Common/TableEntity.tsx delete mode 100644 src/Explorer/Panes/Tables/AddTableEntityPane.ts create mode 100644 src/Explorer/Panes/Tables/AddTableEntityPanel.test.tsx create mode 100644 src/Explorer/Panes/Tables/AddTableEntityPanel.tsx create mode 100644 src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx create mode 100644 src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap diff --git a/.eslintignore b/.eslintignore index 331c2a7e0..4ec15e766 100644 --- a/.eslintignore +++ b/.eslintignore @@ -128,7 +128,6 @@ src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/RenewAdHocAccessPane.ts src/Explorer/Panes/StringInputPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts -src/Explorer/Panes/Tables/AddTableEntityPane.ts src/Explorer/Panes/Tables/EditTableEntityPane.ts src/Explorer/Panes/Tables/EntityPropertyViewModel.ts src/Explorer/Panes/Tables/TableEntityPane.ts diff --git a/src/Common/EntityValue.tsx b/src/Common/EntityValue.tsx new file mode 100644 index 000000000..16d95a007 --- /dev/null +++ b/src/Common/EntityValue.tsx @@ -0,0 +1,61 @@ +import { DatePicker, TextField } from "office-ui-fabric-react"; +import React, { FunctionComponent } from "react"; + +export interface TableEntityProps { + entityValueLabel?: string; + entityValuePlaceholder: string; + entityValue: string | Date; + isEntityTypeDate: boolean; + entityTimeValue: string; + entityValueType: string; + onEntityValueChange: (event: React.FormEvent, newInput?: string) => void; + onSelectDate: (date: Date | null | undefined) => void; + onEntityTimeValueChange: (event: React.FormEvent, newInput?: string) => void; +} + +export const EntityValue: FunctionComponent = ({ + entityValueLabel, + entityValuePlaceholder, + entityValue, + isEntityTypeDate, + entityTimeValue, + entityValueType, + onEntityValueChange, + onSelectDate, + onEntityTimeValueChange, +}: TableEntityProps): JSX.Element => { + if (isEntityTypeDate) { + return ( + <> + + + + ); + } + + return ( + + ); +}; diff --git a/src/Common/TableEntity.tsx b/src/Common/TableEntity.tsx new file mode 100644 index 000000000..864929752 --- /dev/null +++ b/src/Common/TableEntity.tsx @@ -0,0 +1,136 @@ +import { + Dropdown, + IDropdownOption, + IDropdownStyles, + IImageProps, + Image, + IStackTokens, + Stack, + TextField, + TooltipHost, +} from "office-ui-fabric-react"; +import React, { FunctionComponent } from "react"; +import DeleteIcon from "../../images/delete.svg"; +import EditIcon from "../../images/Edit_entity.svg"; +import { CassandraType, TableType } from "../Explorer/Tables/Constants"; +import { userContext } from "../UserContext"; +import { EntityValue } from "./EntityValue"; + +const dropdownStyles: Partial = { dropdown: { width: 100 } }; + +export interface TableEntityProps { + entityTypeLabel?: string; + entityPropertyLabel?: string; + entityValueLabel?: string; + isDeleteOptionVisible: boolean; + entityProperty: string; + entityPropertyPlaceHolder: string; + selectedKey: string | number; + entityValuePlaceholder: string; + entityValue: string | Date; + isEntityTypeDate: boolean; + options: { key: string; text: string }[]; + isPropertyTypeDisable: boolean; + entityTimeValue: string; + onDeleteEntity?: () => void; + onEditEntity?: () => void; + onEntityPropertyChange: (event: React.FormEvent, newInput?: string) => void; + onEntityTypeChange: (event: React.FormEvent, selectedParam: IDropdownOption) => void; + onEntityValueChange: (event: React.FormEvent, newInput?: string) => void; + onSelectDate: (date: Date | null | undefined) => void; + onEntityTimeValueChange: (event: React.FormEvent, newInput?: string) => void; +} + +export const TableEntity: FunctionComponent = ({ + entityTypeLabel, + entityPropertyLabel, + isDeleteOptionVisible, + entityProperty, + selectedKey, + entityPropertyPlaceHolder, + entityValueLabel, + entityValuePlaceholder, + entityValue, + options, + isPropertyTypeDisable, + isEntityTypeDate, + entityTimeValue, + onEditEntity, + onDeleteEntity, + onEntityPropertyChange, + onEntityTypeChange, + onEntityValueChange, + onSelectDate, + onEntityTimeValueChange, +}: TableEntityProps): JSX.Element => { + const imageProps: IImageProps = { + width: 16, + height: 30, + className: entityPropertyLabel ? "addRemoveIconLabel" : "addRemoveIcon", + }; + + const sectionStackTokens: IStackTokens = { childrenGap: 12 }; + + const getEntityValueType = (): string => { + const { Int, Smallint, Tinyint } = CassandraType; + const { Double, Int32, Int64 } = TableType; + + if ( + selectedKey === Double || + selectedKey === Int32 || + selectedKey === Int64 || + selectedKey === Int || + selectedKey === Smallint || + selectedKey === Tinyint + ) { + return "number"; + } + return "string"; + }; + + return ( + <> + + + + + + editEntity + + + {isDeleteOptionVisible && userContext.apiType !== "Cassandra" && ( + + delete entity + + )} + + + ); +}; diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index af32c25a4..c9c5fab96 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -177,42 +177,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, EditTableEntityPane { "addButtonLabel": "Add Property", "attributeNameLabel": "Property Name", @@ -453,42 +417,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "addDatabaseText": [Function], - "addTableEntityPane": AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, "arcadiaToken": [Function], "canExceedMaximumValue": [Function], "canSaveQueries": [Function], @@ -911,42 +839,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, EditTableEntityPane { "addButtonLabel": "Add Property", "attributeNameLabel": "Property Name", @@ -1187,42 +1079,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "addDatabaseText": [Function], - "addTableEntityPane": AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, "arcadiaToken": [Function], "canExceedMaximumValue": [Function], "canSaveQueries": [Function], @@ -1658,42 +1514,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, EditTableEntityPane { "addButtonLabel": "Add Property", "attributeNameLabel": "Property Name", @@ -1934,42 +1754,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "addDatabaseText": [Function], - "addTableEntityPane": AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, "arcadiaToken": [Function], "canExceedMaximumValue": [Function], "canSaveQueries": [Function], @@ -2392,42 +2176,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, EditTableEntityPane { "addButtonLabel": "Add Property", "attributeNameLabel": "Property Name", @@ -2668,42 +2416,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "addDatabaseText": [Function], - "addTableEntityPane": AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, "arcadiaToken": [Function], "canExceedMaximumValue": [Function], "canSaveQueries": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index a9d538df2..b1fa3035e 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -63,14 +63,16 @@ import { SaveQueryPanel } from "./Panes/SaveQueryPanel"; import { SettingsPane } from "./Panes/SettingsPane"; import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { StringInputPane } from "./Panes/StringInputPane"; -import AddTableEntityPane from "./Panes/Tables/AddTableEntityPane"; +import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel"; import { UploadFilePane } from "./Panes/UploadFilePane"; import { UploadItemsPane } from "./Panes/UploadItemsPane"; +import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel"; import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab"; +import QueryTablesTab from "./Tabs/QueryTablesTab"; import TabsBase from "./Tabs/TabsBase"; import { TabsManager } from "./Tabs/TabsManager"; import TerminalTab from "./Tabs/TerminalTab"; @@ -177,7 +179,6 @@ export default class Explorer { public addDatabasePane: AddDatabasePane; public addCollectionPane: AddCollectionPane; public graphStylingPane: GraphStylingPane; - public addTableEntityPane: AddTableEntityPane; public editTableEntityPane: EditTableEntityPane; public newVertexPane: NewVertexPane; public cassandraAddCollectionPane: CassandraAddCollectionPane; @@ -514,13 +515,6 @@ export default class Explorer { container: this, }); - this.addTableEntityPane = new AddTableEntityPane({ - id: "addtableentitypane", - visible: ko.observable(false), - - container: this, - }); - this.editTableEntityPane = new EditTableEntityPane({ id: "edittableentitypane", visible: ko.observable(false), @@ -561,7 +555,6 @@ export default class Explorer { this.addDatabasePane, this.addCollectionPane, this.graphStylingPane, - this.addTableEntityPane, this.editTableEntityPane, this.newVertexPane, this.cassandraAddCollectionPane, @@ -634,7 +627,6 @@ export default class Explorer { this.addCollectionPane.collectionIdTitle("Table id"); this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table"); this.refreshTreeTitle("Refresh tables"); - this.addTableEntityPane.title("Add Table Entity"); this.editTableEntityPane.title("Edit Table Entity"); this.tableDataClient = new TablesAPIDataClient(); break; @@ -649,7 +641,6 @@ export default class Explorer { this.addCollectionPane.collectionIdTitle("Table id"); this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table"); this.refreshTreeTitle("Refresh tables"); - this.addTableEntityPane.title("Add Table Row"); this.editTableEntityPane.title("Edit Table Row"); this.tableDataClient = new CassandraAPIDataClient(); break; @@ -2263,6 +2254,18 @@ export default class Explorer { ); } + public openAddTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void { + this.openSidePanel( + "Add Table Entity", + + ); + } public openSetupNotebooksPanel(title: string, description: string): void { this.openSidePanel( title, diff --git a/src/Explorer/Panes/PanelComponent.less b/src/Explorer/Panes/PanelComponent.less index 9df4a5771..03ea9dd6d 100644 --- a/src/Explorer/Panes/PanelComponent.less +++ b/src/Explorer/Panes/PanelComponent.less @@ -152,6 +152,21 @@ .removeIcon { color: @InfoIconColor; } +.backImageIcon { + margin-top: 8px; +} +.entityValueTextField { + margin: 24px; +} +.addEntityDatePicker { + max-width: 145px; +} +.addEntityTextField { + width: 237px; +} +.addButtonEntiy { + width: 25%; +} .column-select-view { margin: 20px 0px 0px 0px; } diff --git a/src/Explorer/Panes/PanelContainerComponent.tsx b/src/Explorer/Panes/PanelContainerComponent.tsx index 008fd2089..1233fded3 100644 --- a/src/Explorer/Panes/PanelContainerComponent.tsx +++ b/src/Explorer/Panes/PanelContainerComponent.tsx @@ -1,5 +1,5 @@ +import { IPanelProps, IRenderFunction, Panel, PanelType } from "office-ui-fabric-react"; import * as React from "react"; -import { Panel, PanelType } from "office-ui-fabric-react"; export interface PanelContainerProps { headerText: string; @@ -7,6 +7,8 @@ export interface PanelContainerProps { isConsoleExpanded: boolean; isOpen: boolean; closePanel: () => void; + panelWidth?: string; + onRenderNavigationContent?: IRenderFunction; } export interface PanelContainerState { @@ -46,8 +48,9 @@ export class PanelContainerComponent extends React.Component("addEntityScroll"); - } - - public submit() { - if (!this.canApply()) { - return; - } - let entity: Entities.ITableEntity = this.entityFromAttributes(this.displayedAttributes()); - this.container.tableDataClient - .createDocument(this.tableViewModel.queryTablesTab.collection, entity) - .then((newEntity: Entities.ITableEntity) => { - this.tableViewModel.addEntityToCache(newEntity).then(() => { - if (!this.tryInsertNewHeaders(this.tableViewModel, newEntity)) { - this.tableViewModel.redrawTableThrottled(); - } - }); - this.close(); - }); - } - - public open() { - var headers = this.tableViewModel.headers; - if (DataTableUtilities.checkForDefaultHeader(headers)) { - headers = []; - if (this.container.isPreferredApiTable()) { - headers = [TableConstants.EntityKeyNames.PartitionKey, TableConstants.EntityKeyNames.RowKey]; - } - } - if (userContext.apiType === "Cassandra") { - (this.container.tableDataClient) - .getTableSchema(this.tableViewModel.queryTablesTab.collection) - .then((columns: CassandraTableKey[]) => { - this.displayedAttributes( - this.constructDisplayedAttributes( - columns.map((col) => col.property), - Utilities.getDataTypesFromCassandraSchema(columns) - ) - ); - this.updateIsActionEnabled(); - super.open(); - this.focusValueElement(); - }); - } else { - this.displayedAttributes( - this.constructDisplayedAttributes( - headers, - Utilities.getDataTypesFromEntities(headers, this.tableViewModel.items()) - ) - ); - this.updateIsActionEnabled(); - super.open(); - this.focusValueElement(); - } - } - - private focusValueElement() { - const focusElement = document.getElementById("addTableEntityValue"); - focusElement && focusElement.focus(); - } - - private constructDisplayedAttributes(headers: string[], dataTypes: any): EntityPropertyViewModel[] { - var displayedAttributes: EntityPropertyViewModel[] = []; - headers && - headers.forEach((key: string) => { - if (!_.contains(AddTableEntityPane._excludedFields, key)) { - if (userContext.apiType === "Cassandra") { - const cassandraKeys = this.tableViewModel.queryTablesTab.collection.cassandraKeys.partitionKeys - .concat(this.tableViewModel.queryTablesTab.collection.cassandraKeys.clusteringKeys) - .map((key) => key.property); - var isRequired: boolean = _.contains(cassandraKeys, key); - var editable: boolean = false; - var placeholderLabel: string = isRequired ? this.enterRequiredValueLabel : this.enterValueLabel; - var entityAttributeType: string = dataTypes[key] || TableConstants.CassandraType.Text; // Default to String if there is no type specified. - // TODO figure out validation story for blob and Inet so we can allow adding/editing them - const nonEditableType: boolean = - entityAttributeType === TableConstants.CassandraType.Blob || - entityAttributeType === TableConstants.CassandraType.Inet; - var entity: EntityPropertyViewModel = new EntityPropertyViewModel( - this, - key, - entityAttributeType, - "", // default to empty string - /* namePlaceholder */ undefined, - nonEditableType ? "Type is not editable via DataExplorer." : placeholderLabel, - editable, - /* default valid name */ true, - /* default valid value */ true, - /* required value */ isRequired, - /* removable */ false, - /* valueEditable */ !nonEditableType, - /* ignoreEmptyValue */ true - ); - } else { - var isRequired: boolean = _.contains(AddTableEntityPane.requiredFieldsForTablesAPI, key); - var editable: boolean = !_.contains(AddTableEntityPane._readonlyFields, key); - var placeholderLabel: string = isRequired ? this.enterRequiredValueLabel : this.enterValueLabel; - var entityAttributeType: string = dataTypes[key] || TableConstants.TableType.String; // Default to String if there is no type specified. - var entity: EntityPropertyViewModel = new EntityPropertyViewModel( - this, - key, - entityAttributeType, - "", // default to empty string - /* namePlaceholder */ undefined, - placeholderLabel, - editable, - /* default valid name */ true, - /* default valid value */ true, - /* required value */ isRequired, - /* removable */ editable, - /* valueEditable */ true, - /* ignoreEmptyValue */ true - ); - } - displayedAttributes.push(entity); - } - }); - - return displayedAttributes; - } -} diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.test.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.test.tsx new file mode 100644 index 000000000..135bcaac9 --- /dev/null +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.test.tsx @@ -0,0 +1,49 @@ +import { mount } from "enzyme"; +import * as ko from "knockout"; +import React from "react"; +import Explorer from "../../Explorer"; +import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel"; +import * as Entities from "../../Tables/Entities"; +import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; +import QueryTablesTab from "../../Tabs/QueryTablesTab"; +import { AddTableEntityPanel } from "./AddTableEntityPanel"; + +describe("Excute Add Table Entity Pane", () => { + const fakeExplorer = {} as Explorer; + const fakeQueryTablesTab = {} as QueryTablesTab; + const fakeTableEntityListViewModel = {} as TableListViewModal; + const fakeCassandraApiClient = {} as CassandraAPIDataClient; + fakeTableEntityListViewModel.items = ko.observableArray(); + fakeTableEntityListViewModel.headers = []; + const props = { + explorer: fakeExplorer, + closePanel: (): void => undefined, + queryTablesTab: fakeQueryTablesTab, + tableEntityListViewModel: fakeTableEntityListViewModel, + cassandraApiClient: fakeCassandraApiClient, + }; + + it("should render Default properly", () => { + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + }); + + it("initially display 4 input field, 2 properties and 2 entity values", () => { + const wrapper = mount(); + expect(wrapper.find("input[type='text']")).toHaveLength(0); + }); + + it("add a new entity row", () => { + const wrapper = mount(); + wrapper.find(".addButtonEntiy").last().simulate("click"); + expect(wrapper.find("input[type='text']")).toHaveLength(1); + }); + + it("remove a entity field", () => { + const wrapper = mount(); + // Since default entity row doesn't have delete option, so added row then delete for test cases. + wrapper.find(".addButtonEntiy").last().simulate("click"); + wrapper.find("#deleteEntity").last().simulate("click"); + expect(wrapper.find("input[type='text']")).toHaveLength(0); + }); +}); diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx new file mode 100644 index 000000000..ca28ce96e --- /dev/null +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx @@ -0,0 +1,324 @@ +import { useBoolean } from "@uifabric/react-hooks"; +import { + IDropdownOption, + Image, + IPanelProps, + IRenderFunction, + Label, + Stack, + Text, + TextField, +} from "office-ui-fabric-react"; +import React, { FunctionComponent, useEffect, useState } from "react"; +import * as _ from "underscore"; +import AddPropertyIcon from "../../../../images/Add-property.svg"; +import RevertBackIcon from "../../../../images/RevertBack.svg"; +import { TableEntity } from "../../../Common/TableEntity"; +import { userContext } from "../../../UserContext"; +import Explorer from "../../Explorer"; +import * as TableConstants from "../../Tables/Constants"; +import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; +import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel"; +import * as Entities from "../../Tables/Entities"; +import { CassandraAPIDataClient, CassandraTableKey } from "../../Tables/TableDataClient"; +import * as TableEntityProcessor from "../../Tables/TableEntityProcessor"; +import * as Utilities from "../../Tables/Utilities"; +import QueryTablesTab from "../../Tabs/QueryTablesTab"; +import { PanelContainerComponent } from "../PanelContainerComponent"; +import { + attributeNameLabel, + attributeValueLabel, + backImageProps, + cassandraOptions, + columnProps, + dataTypeLabel, + detailedHelp, + entityFromAttributes, + getAddButtonLabel, + getButtonLabel, + getCassandraDefaultEntities, + getDefaultEntities, + getEntityValuePlaceholder, + getPanelTitle, + imageProps, + isValidEntities, + options, +} from "./Validators/EntityTableHelper"; + +interface AddTableEntityPanelProps { + explorer: Explorer; + closePanel: () => void; + queryTablesTab: QueryTablesTab; + tableEntityListViewModel: TableEntityListViewModel; + cassandraApiClient: CassandraAPIDataClient; +} + +interface EntityRowType { + property: string; + type: string; + value: string; + isPropertyTypeDisable: boolean; + isDeleteOptionVisible: boolean; + id: number; + entityValuePlaceholder: string; + isEntityTypeDate: boolean; + entityTimeValue?: string; +} + +export const AddTableEntityPanel: FunctionComponent = ({ + explorer, + closePanel, + queryTablesTab, + tableEntityListViewModel, + cassandraApiClient, +}: AddTableEntityPanelProps): JSX.Element => { + const [entities, setEntities] = useState([]); + const [selectedRow, setSelectedRow] = useState(0); + const [entityAttributeValue, setEntityAttributeValue] = useState(""); + const [entityAttributeProperty, setEntityAttributeProperty] = useState(""); + const [ + isEntityValuePanelOpen, + { setTrue: setIsEntityValuePanelTrue, setFalse: setIsEntityValuePanelFalse }, + ] = useBoolean(false); + + /* Get default and previous saved entity headers */ + useEffect(() => { + getDefaultEntitiesAttribute(); + }, []); + + const getDefaultEntitiesAttribute = async (): Promise => { + let headers = tableEntityListViewModel.headers; + if (DataTableUtilities.checkForDefaultHeader(headers)) { + headers = []; + if (userContext.apiType === "Tables") { + headers = [TableConstants.EntityKeyNames.PartitionKey, TableConstants.EntityKeyNames.RowKey]; + } + } + if (userContext.apiType === "Cassandra") { + const columns: CassandraTableKey[] = await cassandraApiClient.getTableSchema(queryTablesTab.collection); + const cassandraEntities = Utilities.getDataTypesFromCassandraSchema(columns); + const cassandraDefaultEntities: EntityRowType[] = getCassandraDefaultEntities(headers, cassandraEntities); + setEntities(cassandraDefaultEntities); + } else { + const entityItems = tableEntityListViewModel.items(); + const entityTypes = Utilities.getDataTypesFromEntities(headers, entityItems); + const defaultEntities: EntityRowType[] = getDefaultEntities(headers, entityTypes); + setEntities(defaultEntities); + } + }; + + /* Add new entity attribute */ + const submit = async (event: React.FormEvent): Promise => { + if (!isValidEntities(entities)) { + return undefined; + } + event.preventDefault(); + + const entity: Entities.ITableEntity = entityFromAttributes(entities); + const newEntity: Entities.ITableEntity = await explorer.tableDataClient.createDocument( + queryTablesTab.collection, + entity + ); + await tableEntityListViewModel.addEntityToCache(newEntity); + if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { + tableEntityListViewModel.redrawTableThrottled(); + } + closePanel(); + }; + + const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => { + let newHeaders: string[] = []; + const keys = Object.keys(newEntity); + keys && + keys.forEach((key: string) => { + if ( + !_.contains(viewModel.headers, key) && + key !== TableEntityProcessor.keyProperties.attachments && + key !== TableEntityProcessor.keyProperties.etag && + key !== TableEntityProcessor.keyProperties.resourceId && + key !== TableEntityProcessor.keyProperties.self && + (!(userContext.apiType === "Cassandra") || key !== TableConstants.EntityKeyNames.RowKey) + ) { + newHeaders.push(key); + } + }); + + let newHeadersInserted = false; + if (newHeaders.length) { + if (!DataTableUtilities.checkForDefaultHeader(viewModel.headers)) { + newHeaders = viewModel.headers.concat(newHeaders); + } + viewModel.updateHeaders(newHeaders, /* notifyColumnChanges */ true, /* enablePrompt */ false); + newHeadersInserted = true; + } + return newHeadersInserted; + }; + + /* Add new entity row */ + const addNewEntity = (): void => { + const cloneEntities: EntityRowType[] = [...entities]; + cloneEntities.splice(cloneEntities.length, 0, { + property: "", + type: "String", + value: "", + isPropertyTypeDisable: false, + isDeleteOptionVisible: true, + id: cloneEntities.length + 1, + entityValuePlaceholder: "", + isEntityTypeDate: false, + }); + setEntities(cloneEntities); + }; + + /* Delete entity row */ + const deleteEntityAtIndex = (indexToRemove: number): void => { + const cloneEntities: EntityRowType[] = [...entities]; + cloneEntities.splice(indexToRemove, 1); + setEntities(cloneEntities); + }; + + /* handle Entity change */ + const entityChange = (value: string | Date, indexOfInput: number, key: string): void => { + const cloneEntities: EntityRowType[] = [...entities]; + if (key === "property") { + cloneEntities[indexOfInput].property = value.toString(); + } else if (key === "time") { + cloneEntities[indexOfInput].entityTimeValue = value.toString(); + } else { + cloneEntities[indexOfInput].value = value.toString(); + } + setEntities(cloneEntities); + }; + + /* handle Entity type */ + const entityTypeChange = ( + _event: React.FormEvent, + selectedType: IDropdownOption, + indexOfEntity: number + ): void => { + const entityValuePlaceholder: string = getEntityValuePlaceholder(selectedType.key); + const cloneEntities: EntityRowType[] = [...entities]; + cloneEntities[indexOfEntity].type = selectedType.key.toString(); + cloneEntities[indexOfEntity].entityValuePlaceholder = entityValuePlaceholder; + cloneEntities[indexOfEntity].isEntityTypeDate = selectedType.key === "DateTime"; + setEntities(cloneEntities); + }; + + /* Open edit entity value modal */ + const editEntity = (rowEndex: number): void => { + const entityAttribute: EntityRowType = entities[rowEndex] && entities[rowEndex]; + setEntityAttributeValue(entityAttribute.value); + setEntityAttributeProperty(entityAttribute.property); + setSelectedRow(rowEndex); + setIsEntityValuePanelTrue(); + }; + + const renderPanelContent = (): JSX.Element => { + return ( +
    +
    +
    + {entities.map((entity, index) => { + return ( + editEntity(index)} + onSelectDate={(date: Date) => { + entityChange(date, index, "value"); + }} + onDeleteEntity={() => deleteEntityAtIndex(index)} + onEntityPropertyChange={(event, newInput?: string) => { + entityChange(newInput, index, "property"); + }} + onEntityTypeChange={(event: React.FormEvent, selectedParam: IDropdownOption) => { + entityTypeChange(event, selectedParam, index); + }} + onEntityValueChange={(event, newInput?: string) => { + entityChange(newInput, index, "value"); + }} + onEntityTimeValueChange={(event, newInput?: string) => { + entityChange(newInput, index, "time"); + }} + /> + ); + })} + {userContext.apiType !== "Cassandra" && ( + + Add Entity + {getAddButtonLabel(userContext.apiType)} + + )} +
    +
    +
    + +
    +
    +
    +
    + ); + }; + + const onRenderNavigationContent: IRenderFunction = () => { + return ( + + back setIsEntityValuePanelFalse()} /> + + + ); + }; + + if (isEntityValuePanelOpen) { + return ( + { + entityChange(newInput, selectedRow, "value"); + setEntityAttributeValue(newInput); + }} + /> + } + closePanel={() => closePanel()} + isConsoleExpanded={false} + /> + ); + } + + return ( + closePanel()} + isConsoleExpanded={false} + /> + ); +}; diff --git a/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx b/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx new file mode 100644 index 000000000..4f08ba83e --- /dev/null +++ b/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx @@ -0,0 +1,247 @@ +import { IImageProps, IStackProps } from "office-ui-fabric-react"; +import * as _ from "underscore"; +import * as TableConstants from "../../../Tables/Constants"; +import * as Entities from "../../../Tables/Entities"; +import * as Utilities from "../../../Tables/Utilities"; + +export const defaultStringPlaceHolder = "Enter identifier value."; +export const defaultEntities = [ + { + property: "PartitionKey", + type: "String", + value: "", + isPropertyTypeDisable: true, + isDeleteOptionVisible: false, + id: 1, + entityValuePlaceholder: defaultStringPlaceHolder, + isEntityTypeDate: false, + }, + { + property: "RowKey", + type: "String", + value: "", + isPropertyTypeDisable: true, + isDeleteOptionVisible: false, + id: 2, + entityValuePlaceholder: defaultStringPlaceHolder, + isEntityTypeDate: false, + }, +]; + +// Dropdown options +const { String, Boolean, Binary, DateTime, Double, Guid, Int32, Int64 } = TableConstants.TableType; +export const options = [ + { key: String, text: String }, + { key: Boolean, text: Boolean }, + { key: Binary, text: Binary, disabled: true }, + { key: DateTime, text: DateTime }, + { key: Double, text: Double }, + { key: Guid, text: Guid }, + { key: Int32, text: Int32 }, + { key: Int64, text: Int64 }, +]; + +const { + Text, + Ascii, + Bigint, + Blob, + Decimal, + Float, + Int, + Uuid, + Varchar, + Varint, + Inet, + Smallint, + Tinyint, +} = TableConstants.CassandraType; +export const cassandraOptions = [ + { key: Text, text: Text }, + { key: Ascii, text: Ascii }, + { key: Bigint, text: Bigint }, + { key: Blob, text: Blob }, + { key: Boolean, text: Boolean }, + { key: Decimal, text: Decimal }, + { key: Double, text: Double }, + { key: Float, text: Float }, + { key: Int, text: Int }, + { key: Uuid, text: Uuid }, + { key: Varchar, text: Varchar }, + { key: Varint, text: Varint }, + { key: Inet, text: Inet }, + { key: Smallint, text: Smallint }, + { key: Tinyint, text: Tinyint }, +]; + +export const imageProps: IImageProps = { + width: 16, + height: 30, +}; + +export const backImageProps: IImageProps = { + width: 16, + height: 16, + className: "backImageIcon", +}; +/* Labels */ +export const attributeNameLabel = "Property Name"; +export const dataTypeLabel = "Type"; +export const attributeValueLabel = "Value"; +export const addButtonLabel = "Add Property"; + +// add table entity placeholders +export const detailedHelp = "Enter a name up to 255 characters in size. Most valid C# identifiers are allowed."; +export const booleanPlaceHolder = "Enter true or false."; +export const stringPlaceholder = "Enter a value up to 64 KB in size."; +export const datePlaceholder = "Enter a date and time."; +export const doublePlaceholder = "Enter a 64-bit floating point value."; +export const guidPlaceholder = "Enter a 16-byte (128-bit) GUID value."; +export const intPlaceholder = "Enter a signed 32-bit integer."; +export const int64Placeholder = "Enter a signed 64-bit integer, in the range (-2^53 - 1, 2^53 - 1)."; + +export const columnProps: Partial = { + tokens: { childrenGap: 10 }, + styles: { root: { width: 680 } }, +}; + +// helper functions +export const entityFromAttributes = (entities: EntityRowType[]): Entities.ITableEntity => { + const entity: { [key: string]: { _: string; $: string } } = {}; + entities.forEach((entityRow: EntityRowType) => { + if (entityRow) { + let value = entityRow.value; + if (entityRow.type === TableConstants.TableType.DateTime && entityRow.entityTimeValue) { + // Add time in date as time has seperate textfield + const [hours, minuntes] = entityRow.entityTimeValue.split(":"); + const entityDate = new Date(value); + entityDate.setHours(+hours); + entityDate.setMinutes(+minuntes); + value = entityDate.toString(); + } + if (entityRow.type === TableConstants.TableType.Int64) { + value = Utilities.padLongWithZeros(value); + } + entity[entityRow.property] = { + _: value, + $: entityRow.type, + }; + } + }); + return entity; +}; + +// GetPlaceholder according to entity type +export const getEntityValuePlaceholder = (entityType: string | number): string => { + switch (entityType) { + case "String": + return stringPlaceholder; + case "Boolean": + return booleanPlaceHolder; + case "DateTime": + return datePlaceholder; + case "Double": + return doublePlaceholder; + case "Guid": + return guidPlaceholder; + case "Int32": + return intPlaceholder; + case "Int64": + return int64Placeholder; + default: + return ""; + } +}; + +export const isValidEntities = (entities: EntityRowType[]): boolean => { + for (let i = 0; i < entities.length; i++) { + const { property } = entities[i]; + if (property === "" || property === undefined) { + return false; + } + } + return true; +}; + +const isEntityPropertyTypeDisable = (header: string): boolean => { + if (header === "PartitionKey" || header === "RowKey") { + return true; + } + return false; +}; + +export const getDefaultEntities = (headers: string[], entityTypes: { [key: string]: string }): EntityRowType[] => { + const defaultEntities: EntityRowType[] = []; + headers.forEach((header: string) => { + if (header !== "Timestamp") { + const entityType = !_.isEmpty(entityTypes) ? entityTypes[header] : "String"; + const entityRow = { + property: header, + type: entityType, + value: "", + isPropertyTypeDisable: isEntityPropertyTypeDisable(header), + isDeleteOptionVisible: !isEntityPropertyTypeDisable(header), + id: 1, + entityValuePlaceholder: defaultStringPlaceHolder, + isEntityTypeDate: entityType === "DateTime", + }; + defaultEntities.push(entityRow); + } + }); + return defaultEntities; +}; + +export const getPanelTitle = (apiType: string): string => { + if (apiType === "Cassandra") { + return "Add Table Row"; + } + return "Add Table Row"; +}; + +export const getAddButtonLabel = (apiType: string): string => { + if (apiType === "Cassandra") { + return "Add Row"; + } + return addButtonLabel; +}; + +export const getButtonLabel = (apiType: string): string => { + if (apiType === "Cassandra") { + return "Add Row"; + } + return "Add Entity"; +}; + +export const getCassandraDefaultEntities = ( + headers: string[], + entityTypes: { [key: string]: string } +): EntityRowType[] => { + const defaultEntities: EntityRowType[] = []; + headers.forEach((header: string) => { + const entityRow = { + property: header, + type: entityTypes[header], + value: "", + isPropertyTypeDisable: true, + isDeleteOptionVisible: true, + id: 1, + entityValuePlaceholder: defaultStringPlaceHolder, + isEntityTypeDate: entityTypes[header] === "DateTime", + }; + defaultEntities.push(entityRow); + }); + return defaultEntities; +}; + +// Type of entity row +export interface EntityRowType { + property: string; + type: string; + value: string; + isPropertyTypeDisable: boolean; + isDeleteOptionVisible: boolean; + id: number; + entityValuePlaceholder: string; + isEntityTypeDate: boolean; + entityTimeValue?: string; +} diff --git a/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap b/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap new file mode 100644 index 000000000..6c7982658 --- /dev/null +++ b/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap @@ -0,0 +1,3490 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Excute Add Table Entity Pane should render Default properly 1`] = ` + + +
    +
    + + + + Add Property + + +
    +
    +
    + +
    +
    +
    + + } + panelWidth="700px" + > + + + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + } + > + + +
    + +
    +
    + + +
    + + + +
    +
    +
    +
    +
    +
    + Add Table Row +
    +
    + + + *": Object { + "left": 0, + "position": "relative", + "top": 0, + }, + }, + "textAlign": "center", + "textDecoration": "none", + "userSelect": "none", + }, + Object { + "backgroundColor": "transparent", + "border": "none", + "color": "#0078d4", + "height": "32px", + "padding": "0 4px", + "width": "32px", + }, + "ms-Panel-closeButton ms-PanelAction-close", + Object { + "color": "#605e5c", + "fontSize": "20px", + "marginRight": 14, + }, + false, + ], + "rootChecked": Object { + "backgroundColor": "#edebe9", + "color": "#005a9e", + }, + "rootCheckedHovered": Object { + "backgroundColor": "#e1dfdd", + "color": "#005a9e", + }, + "rootDisabled": Array [ + Object { + "outline": "transparent", + "position": "relative", + "selectors": Object { + ".ms-Fabric--isFocusVisible &:focus:after": Object { + "border": "1px solid transparent", + "bottom": 2, + "content": "\\"\\"", + "left": 2, + "outline": "1px solid #605e5c", + "position": "absolute", + "right": 2, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "bottom": -2, + "left": -2, + "outlineColor": "ButtonText", + "right": -2, + "top": -2, + }, + }, + "top": 2, + "zIndex": 1, + }, + "::-moz-focus-inner": Object { + "border": "0", + }, + }, + }, + Object { + "backgroundColor": "#f3f2f1", + "borderColor": "#f3f2f1", + "color": "#a19f9d", + "cursor": "default", + "pointerEvents": "none", + "selectors": Object { + ":focus": Object { + "outline": 0, + }, + ":hover": Object { + "outline": 0, + }, + }, + }, + Object { + "color": "#c8c6c4", + }, + ], + "rootExpanded": Object { + "backgroundColor": "#edebe9", + "color": "#005a9e", + }, + "rootHasMenu": Object { + "width": "auto", + }, + "rootHovered": Array [ + Object { + "backgroundColor": "#f3f2f1", + "color": "#106ebe", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "borderColor": "Highlight", + "color": "Highlight", + }, + }, + }, + Object { + "color": "#323130", + }, + ], + "rootPressed": Object { + "backgroundColor": "#edebe9", + "color": "#005a9e", + }, + "screenReaderText": Object { + "border": 0, + "height": 1, + "margin": -1, + "overflow": "hidden", + "padding": 0, + "position": "absolute", + "width": 1, + }, + "splitButtonContainer": Array [ + Object { + "outline": "transparent", + "position": "relative", + "selectors": Object { + ".ms-Fabric--isFocusVisible &:focus:after": Object { + "border": "1px solid #ffffff", + "bottom": 3, + "content": "\\"\\"", + "left": 3, + "outline": "1px solid #605e5c", + "position": "absolute", + "right": 3, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "border": "none", + "bottom": -2, + "left": -2, + "right": -2, + "top": -2, + }, + }, + "top": 3, + "zIndex": 1, + }, + "::-moz-focus-inner": Object { + "border": "0", + }, + }, + }, + Object { + "display": "inline-flex", + "selectors": Object { + ".ms-Button--default": Object { + "borderBottomRightRadius": "0", + "borderRight": "none", + "borderTopRightRadius": "0", + }, + ".ms-Button--primary": Object { + "border": "none", + "borderBottomRightRadius": "0", + "borderTopRightRadius": "0", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "Window", + "border": "1px solid WindowText", + "borderRightWidth": "0", + "color": "WindowText", + "forcedColorAdjust": "none", + }, + }, + }, + ".ms-Button--primary + .ms-Button": Object { + "border": "none", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "border": "1px solid WindowText", + "borderLeftWidth": "0", + }, + }, + }, + }, + }, + ], + "splitButtonContainerChecked": Object { + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + }, + }, + "splitButtonContainerCheckedHovered": Object { + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + }, + }, + "splitButtonContainerDisabled": Object { + "border": "none", + "outline": "none", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + "forcedColorAdjust": "none", + }, + }, + }, + "splitButtonContainerFocused": Object { + "outline": "none!important", + }, + "splitButtonContainerHovered": Object { + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Highlight", + "color": "Window", + }, + }, + }, + ".ms-Button.is-disabled": Object { + "color": "#a19f9d", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + }, + }, + }, + }, + }, + "splitButtonDivider": Object { + "bottom": 8, + "position": "absolute", + "right": 31, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "WindowText", + }, + }, + "top": 8, + "width": 1, + }, + "splitButtonDividerDisabled": Object { + "bottom": 8, + "position": "absolute", + "right": 31, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "GrayText", + }, + }, + "top": 8, + "width": 1, + }, + "splitButtonFlexContainer": Object { + "alignItems": "center", + "display": "flex", + "flexWrap": "nowrap", + "height": "100%", + "justifyContent": "center", + }, + "splitButtonMenuButton": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + ".ms-Button-menuIcon": Object { + "color": "WindowText", + }, + }, + "border": "1px solid #8a8886", + "borderBottomRightRadius": "2px", + "borderLeft": "none", + "borderRadius": 0, + "borderTopRightRadius": "2px", + "boxSizing": "border-box", + "cursor": "pointer", + "display": "inline-block", + "height": "auto", + "marginBottom": 0, + "marginLeft": -1, + "marginRight": 0, + "marginTop": 0, + "outline": "transparent", + "padding": 6, + "textAlign": "center", + "textDecoration": "none", + "userSelect": "none", + "verticalAlign": "top", + "width": 32, + }, + "splitButtonMenuButtonDisabled": Object { + "border": "none", + "pointerEvents": "none", + "selectors": Object { + ".ms-Button--primary": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + }, + }, + }, + ".ms-Button-menuIcon": Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "color": "GrayText", + }, + }, + }, + ":hover": Object { + "cursor": "default", + }, + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "border": "1px solid GrayText", + "color": "GrayText", + }, + }, + }, + "textContainer": Object { + "display": "block", + "flexGrow": 1, + }, + } + } + theme={ + Object { + "disableGlobalClassNames": false, + "effects": Object { + "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", + "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", + "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", + "elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)", + "roundedCorner2": "2px", + "roundedCorner4": "4px", + "roundedCorner6": "6px", + }, + "fonts": Object { + "large": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "18px", + "fontWeight": 400, + }, + "medium": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "14px", + "fontWeight": 400, + }, + "mediumPlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "16px", + "fontWeight": 400, + }, + "mega": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "68px", + "fontWeight": 600, + }, + "small": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "12px", + "fontWeight": 400, + }, + "smallPlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "12px", + "fontWeight": 400, + }, + "superLarge": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "42px", + "fontWeight": 600, + }, + "tiny": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "10px", + "fontWeight": 400, + }, + "xLarge": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "20px", + "fontWeight": 600, + }, + "xLargePlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "24px", + "fontWeight": 600, + }, + "xSmall": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "10px", + "fontWeight": 400, + }, + "xxLarge": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "28px", + "fontWeight": 600, + }, + "xxLargePlus": Object { + "MozOsxFontSmoothing": "grayscale", + "WebkitFontSmoothing": "antialiased", + "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", + "fontSize": "32px", + "fontWeight": 600, + }, + }, + "isInverted": false, + "palette": Object { + "accent": "#0078d4", + "black": "#000000", + "blackTranslucent40": "rgba(0,0,0,.4)", + "blue": "#0078d4", + "blueDark": "#002050", + "blueLight": "#00bcf2", + "blueMid": "#00188f", + "green": "#107c10", + "greenDark": "#004b1c", + "greenLight": "#bad80a", + "magenta": "#b4009e", + "magentaDark": "#5c005c", + "magentaLight": "#e3008c", + "neutralDark": "#201f1e", + "neutralLight": "#edebe9", + "neutralLighter": "#f3f2f1", + "neutralLighterAlt": "#faf9f8", + "neutralPrimary": "#323130", + "neutralPrimaryAlt": "#3b3a39", + "neutralQuaternary": "#d2d0ce", + "neutralQuaternaryAlt": "#e1dfdd", + "neutralSecondary": "#605e5c", + "neutralSecondaryAlt": "#8a8886", + "neutralTertiary": "#a19f9d", + "neutralTertiaryAlt": "#c8c6c4", + "orange": "#d83b01", + "orangeLight": "#ea4300", + "orangeLighter": "#ff8c00", + "purple": "#5c2d91", + "purpleDark": "#32145a", + "purpleLight": "#b4a0ff", + "red": "#e81123", + "redDark": "#a4262c", + "teal": "#008272", + "tealDark": "#004b50", + "tealLight": "#00b294", + "themeDark": "#005a9e", + "themeDarkAlt": "#106ebe", + "themeDarker": "#004578", + "themeLight": "#c7e0f4", + "themeLighter": "#deecf9", + "themeLighterAlt": "#eff6fc", + "themePrimary": "#0078d4", + "themeSecondary": "#2b88d8", + "themeTertiary": "#71afe5", + "white": "#ffffff", + "whiteTranslucent40": "rgba(255,255,255,.4)", + "yellow": "#ffb900", + "yellowDark": "#d29200", + "yellowLight": "#fff100", + }, + "rtl": undefined, + "semanticColors": Object { + "accentButtonBackground": "#0078d4", + "accentButtonText": "#ffffff", + "actionLink": "#323130", + "actionLinkHovered": "#201f1e", + "blockingBackground": "#FDE7E9", + "blockingIcon": "#FDE7E9", + "bodyBackground": "#ffffff", + "bodyBackgroundChecked": "#edebe9", + "bodyBackgroundHovered": "#f3f2f1", + "bodyDivider": "#edebe9", + "bodyFrameBackground": "#ffffff", + "bodyFrameDivider": "#edebe9", + "bodyStandoutBackground": "#faf9f8", + "bodySubtext": "#605e5c", + "bodyText": "#323130", + "bodyTextChecked": "#000000", + "buttonBackground": "#ffffff", + "buttonBackgroundChecked": "#c8c6c4", + "buttonBackgroundCheckedHovered": "#edebe9", + "buttonBackgroundDisabled": "#f3f2f1", + "buttonBackgroundHovered": "#f3f2f1", + "buttonBackgroundPressed": "#edebe9", + "buttonBorder": "#8a8886", + "buttonBorderDisabled": "#f3f2f1", + "buttonText": "#323130", + "buttonTextChecked": "#201f1e", + "buttonTextCheckedHovered": "#000000", + "buttonTextDisabled": "#a19f9d", + "buttonTextHovered": "#201f1e", + "buttonTextPressed": "#201f1e", + "cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", + "cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)", + "cardStandoutBackground": "#ffffff", + "defaultStateBackground": "#faf9f8", + "disabledBackground": "#f3f2f1", + "disabledBodySubtext": "#c8c6c4", + "disabledBodyText": "#a19f9d", + "disabledBorder": "#c8c6c4", + "disabledSubtext": "#d2d0ce", + "disabledText": "#a19f9d", + "errorBackground": "#FDE7E9", + "errorIcon": "#A80000", + "errorText": "#a4262c", + "focusBorder": "#605e5c", + "infoBackground": "#f3f2f1", + "infoIcon": "#605e5c", + "inputBackground": "#ffffff", + "inputBackgroundChecked": "#0078d4", + "inputBackgroundCheckedHovered": "#005a9e", + "inputBorder": "#605e5c", + "inputBorderHovered": "#323130", + "inputFocusBorderAlt": "#0078d4", + "inputForegroundChecked": "#ffffff", + "inputIcon": "#0078d4", + "inputIconDisabled": "#a19f9d", + "inputIconHovered": "#005a9e", + "inputPlaceholderBackgroundChecked": "#deecf9", + "inputPlaceholderText": "#605e5c", + "inputText": "#323130", + "inputTextHovered": "#201f1e", + "link": "#0078d4", + "linkHovered": "#004578", + "listBackground": "#ffffff", + "listHeaderBackgroundHovered": "#f3f2f1", + "listHeaderBackgroundPressed": "#edebe9", + "listItemBackgroundChecked": "#edebe9", + "listItemBackgroundCheckedHovered": "#e1dfdd", + "listItemBackgroundHovered": "#f3f2f1", + "listText": "#323130", + "listTextColor": "#323130", + "menuBackground": "#ffffff", + "menuDivider": "#c8c6c4", + "menuHeader": "#0078d4", + "menuIcon": "#0078d4", + "menuItemBackgroundChecked": "#edebe9", + "menuItemBackgroundHovered": "#f3f2f1", + "menuItemBackgroundPressed": "#edebe9", + "menuItemText": "#323130", + "menuItemTextHovered": "#201f1e", + "messageLink": "#005A9E", + "messageLinkHovered": "#004578", + "messageText": "#323130", + "primaryButtonBackground": "#0078d4", + "primaryButtonBackgroundDisabled": "#f3f2f1", + "primaryButtonBackgroundHovered": "#106ebe", + "primaryButtonBackgroundPressed": "#005a9e", + "primaryButtonBorder": "transparent", + "primaryButtonText": "#ffffff", + "primaryButtonTextDisabled": "#d2d0ce", + "primaryButtonTextHovered": "#ffffff", + "primaryButtonTextPressed": "#ffffff", + "severeWarningBackground": "#FED9CC", + "severeWarningIcon": "#D83B01", + "smallInputBorder": "#605e5c", + "successBackground": "#DFF6DD", + "successIcon": "#107C10", + "successText": "#107C10", + "variantBorder": "#edebe9", + "variantBorderHovered": "#a19f9d", + "warningBackground": "#FFF4CE", + "warningHighlight": "#ffb900", + "warningIcon": "#797775", + "warningText": "#323130", + }, + "spacing": Object { + "l1": "20px", + "l2": "32px", + "m": "16px", + "s1": "8px", + "s2": "4px", + }, + } + } + title="Close" + variantClassName="ms-Button--icon" + > + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    + Add Entity +
    +
    +
    + + + Add Property + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    , + } + } + /> + + + + + + + + + + + +`; diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap index 1863eb33d..9619eadf7 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap @@ -153,42 +153,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, EditTableEntityPane { "addButtonLabel": "Add Property", "attributeNameLabel": "Property Name", @@ -429,42 +393,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "visible": [Function], }, "addDatabaseText": [Function], - "addTableEntityPane": AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, "arcadiaToken": [Function], "canExceedMaximumValue": [Function], "canSaveQueries": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 08c7d2d8c..572882ac5 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -154,42 +154,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "title": [Function], "visible": [Function], }, - AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, EditTableEntityPane { "addButtonLabel": "Add Property", "attributeNameLabel": "Property Name", @@ -430,42 +394,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "visible": [Function], }, "addDatabaseText": [Function], - "addTableEntityPane": AddTableEntityPane { - "addButtonLabel": "Add Property", - "attributeNameLabel": "Property Name", - "attributeValueLabel": "Value", - "canAdd": [Function], - "canApply": [Function], - "container": [Circular], - "dataTypeLabel": "Type", - "displayedAttributes": [Function], - "editAttribute": [Function], - "editButtonLabel": "Edit", - "editingProperty": [Function], - "edmTypes": [Function], - "enterRequiredValueLabel": "Enter identifier value.", - "enterValueLabel": "Enter value to keep property.", - "finishEditingAttribute": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "addtableentitypane", - "insertAttribute": [Function], - "isEditing": [Function], - "isExecuting": [Function], - "isTemplateReady": [Function], - "onAddPropertyKeyDown": [Function], - "onBackButtonKeyDown": [Function], - "onDeletePropertyKeyDown": [Function], - "onEditPropertyKeyDown": [Function], - "onKeyUp": [Function], - "removeAttribute": [Function], - "removeButtonLabel": "Remove", - "scrollId": [Function], - "submitButtonText": [Function], - "title": [Function], - "visible": [Function], - }, "arcadiaToken": [Function], "canExceedMaximumValue": [Function], "canSaveQueries": [Function], diff --git a/src/Explorer/Tabs/QueryTablesTab.ts b/src/Explorer/Tabs/QueryTablesTab.ts index 70c563a8f..2f8bc77ad 100644 --- a/src/Explorer/Tabs/QueryTablesTab.ts +++ b/src/Explorer/Tabs/QueryTablesTab.ts @@ -146,8 +146,7 @@ export default class QueryTablesTab extends TabsBase { }; public onAddEntityClick = (): Q.Promise => { - this.container.addTableEntityPane.tableViewModel = this.tableEntityListViewModel(); - this.container.addTableEntityPane.open(); + this.container.openAddTableEntityPanel(this, this.tableEntityListViewModel()); return null; }; diff --git a/src/Main.tsx b/src/Main.tsx index ae9c400b7..48b223c82 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -234,7 +234,6 @@ const App: React.FunctionComponent = () => {
    -
    From e705c490c9e0e6edf9f26684a6b50947011556a5 Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Wed, 21 Apr 2021 12:45:34 -0500 Subject: [PATCH 12/23] Retry E2E tests up to 3 times (#711) --- .github/workflows/ci.yml | 23 +---------------------- .github/workflows/cleanup.yml | 28 ++++++++++++++++++++++++++++ test/mongo/container.spec.ts | 6 +++--- test/sql/resourceToken.spec.ts | 4 ++-- test/utils/shared.ts | 2 +- 5 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/cleanup.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0a0f8614..71b677981 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,7 +128,6 @@ jobs: path: failed-* endtoend: name: "E2E" - needs: [cleanupaccounts] runs-on: ubuntu-latest env: NODE_TLS_REJECT_UNAUTHORIZED: 0 @@ -156,33 +155,13 @@ jobs: - name: ${{ matrix['test-file'] }} run: | # Run tests up to three times - n=0 - until [ "$n" -ge 3 ] - do - npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && break - n=$((n+1)) - sleep 1 - done + for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s) shell: bash - uses: actions/upload-artifact@v2 if: failure() with: name: screenshots path: screenshots/ - cleanupaccounts: - name: "Cleanup Test Database Accounts" - runs-on: ubuntu-latest - env: - NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }} - NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} - steps: - - uses: actions/checkout@v2 - - name: Use Node.js 14.x - uses: actions/setup-node@v1 - with: - node-version: 14.x - - run: npm ci - - run: node utils/cleanupDBs.js nuget: name: Publish Nuget if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 000000000..d00fd9724 --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,28 @@ +# This is a basic workflow to help you get started with Actions + +name: Cleanup End to End Account Resources + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + schedule: + # Once every hour + - cron: "0 * * * *" + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + cleanupaccounts: + name: "Cleanup Test Database Accounts" + runs-on: ubuntu-latest + env: + NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }} + NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 14.x + uses: actions/setup-node@v1 + with: + node-version: 14.x + - run: npm ci + - run: node utils/cleanupDBs.js diff --git a/test/mongo/container.spec.ts b/test/mongo/container.spec.ts index 11aa71862..d2f20358f 100644 --- a/test/mongo/container.spec.ts +++ b/test/mongo/container.spec.ts @@ -1,11 +1,11 @@ import { jest } from "@jest/globals"; import "expect-playwright"; import { safeClick } from "../utils/safeClick"; -import { generateUniqueName } from "../utils/shared"; +import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared"; jest.setTimeout(240000); -test("SQL CRUD", async () => { - const databaseId = generateUniqueName("db"); +test("Mongo CRUD", async () => { + const databaseId = generateDatabaseNameWithTimestamp(); const containerId = generateUniqueName("container"); await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner"); diff --git a/test/sql/resourceToken.spec.ts b/test/sql/resourceToken.spec.ts index 8f4d50073..bcaf45ad5 100644 --- a/test/sql/resourceToken.spec.ts +++ b/test/sql/resourceToken.spec.ts @@ -3,7 +3,7 @@ import { CosmosClient, PermissionMode } from "@azure/cosmos"; import * as msRestNodeAuth from "@azure/ms-rest-nodeauth"; import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateDatabaseName, generateUniqueName } from "../utils/shared"; +import { generateUniqueName } from "../utils/shared"; jest.setTimeout(120000); const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4"; @@ -17,7 +17,7 @@ test("Resource token", async () => { const armClient = new CosmosDBManagementClient(credentials, subscriptionId); const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner"); const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner"); - const dbId = generateDatabaseName(); + const dbId = generateUniqueName("db"); const collectionId = generateUniqueName("col"); const client = new CosmosClient({ endpoint: account.documentEndpoint, diff --git a/test/utils/shared.ts b/test/utils/shared.ts index c8c992556..118736129 100644 --- a/test/utils/shared.ts +++ b/test/utils/shared.ts @@ -4,6 +4,6 @@ export function generateUniqueName(baseName = "", length = 4): string { return `${baseName}${crypto.randomBytes(length).toString("hex")}`; } -export function generateDatabaseName(baseName = "db", length = 1): string { +export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string { return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`; } From c1dcd0e90b95274b60c52e20bccc385989cc3573 Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Wed, 21 Apr 2021 13:33:19 -0500 Subject: [PATCH 13/23] Disable Emulator Test (#712) --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71b677981..76bb1325e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,8 @@ jobs: PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} endtoendemulator: name: "End To End Emulator Tests" - if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') + # Temporarily disabled. This test needs to be rewritten in playwright + if: false runs-on: windows-latest steps: - uses: actions/checkout@v2 From cdd6d329909101bef25a9bd7f64aa45ddececa8f Mon Sep 17 00:00:00 2001 From: Hardikkumar Nai <80053762+hardiknai-techm@users.noreply.github.com> Date: Thu, 22 Apr 2021 00:05:32 +0530 Subject: [PATCH 14/23] Rename index.tsx to {class name}.tsx (#689) * Rename index.tsx to {class name}.tsx * Update tests Co-authored-by: Steve Faulkner --- src/Common/Tooltip/{index.tsx => Tooltip.tsx} | 0 src/Common/Upload/{index.tsx => Upload.tsx} | 4 +-- src/Explorer/Explorer.tsx | 26 +++++++++---------- .../BrowseQueriesPane.test.tsx} | 6 ++--- .../BrowseQueriesPane.tsx} | 6 ++--- .../BrowseQueriesPane.test.tsx.snap} | 4 +-- src/Explorer/Panes/CopyNotebookPane.tsx | 21 ++++++++------- ...DeleteCollectionConfirmationPane.test.tsx} | 6 ++--- .../DeleteCollectionConfirmationPane.tsx} | 11 +++++--- ...eCollectionConfirmationPane.test.tsx.snap} | 4 +-- .../ExecuteSprocParamsPane.test.tsx} | 10 +++---- .../ExecuteSprocParamsPane.tsx} | 7 +++-- .../InputParameter.tsx | 0 .../ExecuteSprocParamsPane.test.tsx.snap} | 4 +-- ...ndex.tsx => GenericRightPaneComponent.tsx} | 0 .../LoadQueryPane.test.tsx} | 4 +-- .../LoadQueryPane.tsx} | 13 ++++++---- .../LoadQueryPane.test.tsx.snap} | 0 .../Panes/PublishNotebookPaneAdapter.tsx | 25 ++++++++++-------- .../SaveQueryPane.test.tsx} | 6 ++--- .../SaveQueryPane.tsx} | 11 +++++--- .../SaveQueryPane.test.tsx.snap} | 0 .../{index.test.tsx => SettingsPane.test.tsx} | 2 +- .../{index.tsx => SettingsPane.tsx} | 7 +++-- ...st.tsx.snap => SettingsPane.test.tsx.snap} | 0 .../Tables/TableQuerySelectPanel/index.tsx | 5 +++- .../{index.tsx => UploadFilePane.tsx} | 7 +++-- ...ndex.test.tsx => UploadItemsPane.test.tsx} | 2 +- .../{index.tsx => UploadItemsPane.tsx} | 7 +++-- ...tsx.snap => UploadItemsPane.test.tsx.snap} | 0 30 files changed, 114 insertions(+), 84 deletions(-) rename src/Common/Tooltip/{index.tsx => Tooltip.tsx} (100%) rename src/Common/Upload/{index.tsx => Upload.tsx} (95%) rename src/Explorer/Panes/{BrowseQueriesPanel/index.test.tsx => BrowseQueriesPane/BrowseQueriesPane.test.tsx} (83%) rename src/Explorer/Panes/{BrowseQueriesPanel/index.tsx => BrowseQueriesPane/BrowseQueriesPane.tsx} (93%) rename src/Explorer/Panes/{BrowseQueriesPanel/__snapshots__/index.test.tsx.snap => BrowseQueriesPane/__snapshots__/BrowseQueriesPane.test.tsx.snap} (97%) rename src/Explorer/Panes/{DeleteCollectionConfirmationPanel/index.test.tsx => DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx} (96%) rename src/Explorer/Panes/{DeleteCollectionConfirmationPanel/index.tsx => DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx} (94%) rename src/Explorer/Panes/{DeleteCollectionConfirmationPanel/__snapshots__/index.test.tsx.snap => DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap} (99%) rename src/Explorer/Panes/{ExecuteSprocParamsPanel/index.test.tsx => ExecuteSprocParamsPane/ExecuteSprocParamsPane.test.tsx} (74%) rename src/Explorer/Panes/{ExecuteSprocParamsPanel/index.tsx => ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx} (96%) rename src/Explorer/Panes/{ExecuteSprocParamsPanel => ExecuteSprocParamsPane}/InputParameter.tsx (100%) rename src/Explorer/Panes/{ExecuteSprocParamsPanel/__snapshots__/index.test.tsx.snap => ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap} (99%) rename src/Explorer/Panes/GenericRightPaneComponent/{index.tsx => GenericRightPaneComponent.tsx} (100%) rename src/Explorer/Panes/{LoadQueryPanel/index.test.tsx => LoadQueryPane/LoadQueryPane.test.tsx} (77%) rename src/Explorer/Panes/{LoadQueryPanel/index.tsx => LoadQueryPane/LoadQueryPane.tsx} (93%) rename src/Explorer/Panes/{LoadQueryPanel/__snapshots__/index.test.tsx.snap => LoadQueryPane/__snapshots__/LoadQueryPane.test.tsx.snap} (100%) rename src/Explorer/Panes/{SaveQueryPanel/index.test.tsx => SaveQueryPane/SaveQueryPane.test.tsx} (83%) rename src/Explorer/Panes/{SaveQueryPanel/index.tsx => SaveQueryPane/SaveQueryPane.tsx} (95%) rename src/Explorer/Panes/{SaveQueryPanel/__snapshots__/index.test.tsx.snap => SaveQueryPane/__snapshots__/SaveQueryPane.test.tsx.snap} (100%) rename src/Explorer/Panes/SettingsPane/{index.test.tsx => SettingsPane.test.tsx} (94%) rename src/Explorer/Panes/SettingsPane/{index.tsx => SettingsPane.tsx} (98%) rename src/Explorer/Panes/SettingsPane/__snapshots__/{index.test.tsx.snap => SettingsPane.test.tsx.snap} (100%) rename src/Explorer/Panes/UploadFilePane/{index.tsx => UploadFilePane.tsx} (95%) rename src/Explorer/Panes/UploadItemsPane/{index.test.tsx => UploadItemsPane.test.tsx} (87%) rename src/Explorer/Panes/UploadItemsPane/{index.tsx => UploadItemsPane.tsx} (95%) rename src/Explorer/Panes/UploadItemsPane/__snapshots__/{index.test.tsx.snap => UploadItemsPane.test.tsx.snap} (100%) diff --git a/src/Common/Tooltip/index.tsx b/src/Common/Tooltip/Tooltip.tsx similarity index 100% rename from src/Common/Tooltip/index.tsx rename to src/Common/Tooltip/Tooltip.tsx diff --git a/src/Common/Upload/index.tsx b/src/Common/Upload/Upload.tsx similarity index 95% rename from src/Common/Upload/index.tsx rename to src/Common/Upload/Upload.tsx index 6f0187bad..30cc6b51d 100644 --- a/src/Common/Upload/index.tsx +++ b/src/Common/Upload/Upload.tsx @@ -1,8 +1,8 @@ import { Image, Stack, TextField } from "office-ui-fabric-react"; import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react"; import FolderIcon from "../../../images/folder_16x16.svg"; -import * as Constants from "../../Common/Constants"; -import { Tooltip } from "../Tooltip"; +import * as Constants from "../Constants"; +import { Tooltip } from "../Tooltip/Tooltip"; interface UploadProps { label: string; diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index b1fa3035e..01a746934 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -50,24 +50,24 @@ import { NotebookUtil } from "./Notebook/NotebookUtil"; import AddCollectionPane from "./Panes/AddCollectionPane"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import AddDatabasePane from "./Panes/AddDatabasePane"; -import { BrowseQueriesPanel } from "./Panes/BrowseQueriesPanel"; +import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane"; import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane"; import { ContextualPaneBase } from "./Panes/ContextualPaneBase"; -import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel"; +import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane"; import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel"; -import { ExecuteSprocParamsPanel } from "./Panes/ExecuteSprocParamsPanel"; +import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; import GraphStylingPane from "./Panes/GraphStylingPane"; -import { LoadQueryPanel } from "./Panes/LoadQueryPanel"; +import { LoadQueryPane } from "./Panes/LoadQueryPane/LoadQueryPane"; import NewVertexPane from "./Panes/NewVertexPane"; -import { SaveQueryPanel } from "./Panes/SaveQueryPanel"; -import { SettingsPane } from "./Panes/SettingsPane"; +import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane"; +import { SettingsPane } from "./Panes/SettingsPane/SettingsPane"; import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { StringInputPane } from "./Panes/StringInputPane"; import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel"; -import { UploadFilePane } from "./Panes/UploadFilePane"; -import { UploadItemsPane } from "./Panes/UploadItemsPane"; +import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane"; +import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane"; import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel"; import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; @@ -2179,7 +2179,7 @@ export default class Explorer { let collectionName = PricingUtils.getCollectionName(userContext.defaultExperience); this.openSidePanel( "Delete " + collectionName, - this.closeSidePanel()} @@ -2231,15 +2231,15 @@ export default class Explorer { } public openBrowseQueriesPanel(): void { - this.openSidePanel("Open Saved Queries", ); + this.openSidePanel("Open Saved Queries", ); } public openLoadQueryPanel(): void { - this.openSidePanel("Load Query", this.closeSidePanel()} />); + this.openSidePanel("Load Query", this.closeSidePanel()} />); } public openSaveQueryPanel(): void { - this.openSidePanel("Save Query", this.closeSidePanel()} />); + this.openSidePanel("Save Query", this.closeSidePanel()} />); } public openUploadFilePanel(parent?: NotebookContentItem): void { diff --git a/src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx b/src/Explorer/Panes/BrowseQueriesPane/BrowseQueriesPane.test.tsx similarity index 83% rename from src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx rename to src/Explorer/Panes/BrowseQueriesPane/BrowseQueriesPane.test.tsx index 195aac75c..e052a6616 100644 --- a/src/Explorer/Panes/BrowseQueriesPanel/index.test.tsx +++ b/src/Explorer/Panes/BrowseQueriesPane/BrowseQueriesPane.test.tsx @@ -4,7 +4,7 @@ import React from "react"; import { QueriesClient } from "../../../Common/QueriesClient"; import { Query } from "../../../Contracts/DataModels"; import Explorer from "../../Explorer"; -import { BrowseQueriesPanel } from "./index"; +import { BrowseQueriesPane } from "./BrowseQueriesPane"; describe("Browse queries panel", () => { const fakeExplorer = {} as Explorer; @@ -19,12 +19,12 @@ describe("Browse queries panel", () => { }; it("Should render Default properly", () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); it("Should show empty view when query is empty []", () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.exists("#emptyQueryBanner")).toBe(true); }); }); diff --git a/src/Explorer/Panes/BrowseQueriesPanel/index.tsx b/src/Explorer/Panes/BrowseQueriesPane/BrowseQueriesPane.tsx similarity index 93% rename from src/Explorer/Panes/BrowseQueriesPanel/index.tsx rename to src/Explorer/Panes/BrowseQueriesPane/BrowseQueriesPane.tsx index 83bf6480e..5ad4a036d 100644 --- a/src/Explorer/Panes/BrowseQueriesPanel/index.tsx +++ b/src/Explorer/Panes/BrowseQueriesPane/BrowseQueriesPane.tsx @@ -13,15 +13,15 @@ import { import Explorer from "../../Explorer"; import QueryTab from "../../Tabs/QueryTab"; -interface BrowseQueriesPanelProps { +interface BrowseQueriesPaneProps { explorer: Explorer; closePanel: () => void; } -export const BrowseQueriesPanel: FunctionComponent = ({ +export const BrowseQueriesPane: FunctionComponent = ({ explorer, closePanel, -}: BrowseQueriesPanelProps): JSX.Element => { +}: BrowseQueriesPaneProps): JSX.Element => { const loadSavedQuery = (savedQuery: Query): void => { const selectedCollection: Collection = explorer && explorer.findSelectedCollection(); if (!selectedCollection) { diff --git a/src/Explorer/Panes/BrowseQueriesPanel/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/BrowseQueriesPane/__snapshots__/BrowseQueriesPane.test.tsx.snap similarity index 97% rename from src/Explorer/Panes/BrowseQueriesPanel/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/BrowseQueriesPane/__snapshots__/BrowseQueriesPane.test.tsx.snap index 488ad5cf4..eed894482 100644 --- a/src/Explorer/Panes/BrowseQueriesPanel/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/BrowseQueriesPane/__snapshots__/BrowseQueriesPane.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Browse queries panel Should render Default properly 1`] = ` -
    - + `; diff --git a/src/Explorer/Panes/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane.tsx index 8cc0cdafb..a5b429aa7 100644 --- a/src/Explorer/Panes/CopyNotebookPane.tsx +++ b/src/Explorer/Panes/CopyNotebookPane.tsx @@ -1,18 +1,21 @@ import ko from "knockout"; +import { IDropdownOption } from "office-ui-fabric-react"; import * as React from "react"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; -import { JunoClient, IPinnedRepo } from "../../Juno/JunoClient"; +import { HttpStatusCodes } from "../../Common/Constants"; +import { getErrorMessage, handleError } from "../../Common/ErrorHandlingUtils"; +import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; +import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient"; +import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import Explorer from "../Explorer"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent"; -import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; -import { IDropdownOption } from "office-ui-fabric-react"; -import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; -import { HttpStatusCodes } from "../../Common/Constants"; -import * as GitHubUtils from "../../Utils/GitHubUtils"; -import { NotebookContentItemType, NotebookContentItem } from "../Notebook/NotebookContentItem"; +import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; -import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils"; +import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "./GenericRightPaneComponent/GenericRightPaneComponent"; interface Location { type: "MyNotebooks" | "GitHub"; diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPanel/index.test.tsx b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx similarity index 96% rename from src/Explorer/Panes/DeleteCollectionConfirmationPanel/index.test.tsx rename to src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx index 224ffa89e..ec24e6089 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPanel/index.test.tsx +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx @@ -3,7 +3,6 @@ jest.mock("../../../Shared/Telemetry/TelemetryProcessor"); import { mount, ReactWrapper, shallow } from "enzyme"; import * as ko from "knockout"; import React from "react"; -import { DeleteCollectionConfirmationPanel } from "."; import { deleteCollection } from "../../../Common/dataAccess/deleteCollection"; import DeleteFeedback from "../../../Common/DeleteFeedback"; import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels"; @@ -13,6 +12,7 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; +import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane"; describe("Delete Collection Confirmation Pane", () => { describe("Explorer.isLastCollection()", () => { @@ -65,7 +65,7 @@ describe("Delete Collection Confirmation Pane", () => { closePanel: (): void => undefined, collectionName: "container", }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true); props.explorer.isLastCollection = () => true; @@ -119,7 +119,7 @@ describe("Delete Collection Confirmation Pane", () => { closePanel: (): void => undefined, collectionName: "container", }; - wrapper = mount(); + wrapper = mount(); }); it("should call delete collection", () => { diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPanel/index.tsx b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx similarity index 94% rename from src/Explorer/Panes/DeleteCollectionConfirmationPanel/index.tsx rename to src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx index 0daca44fe..e6a10ff50 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPanel/index.tsx +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx @@ -11,18 +11,21 @@ import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcesso import { userContext } from "../../../UserContext"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; -export interface DeleteCollectionConfirmationPanelProps { +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; +export interface DeleteCollectionConfirmationPaneProps { explorer: Explorer; collectionName: string; closePanel: () => void; } -export const DeleteCollectionConfirmationPanel: FunctionComponent = ({ +export const DeleteCollectionConfirmationPane: FunctionComponent = ({ explorer, closePanel, collectionName, -}: DeleteCollectionConfirmationPanelProps) => { +}: DeleteCollectionConfirmationPaneProps) => { const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState(""); const [inputCollectionName, setInputCollectionName] = useState(""); const [formError, setFormError] = useState(""); diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPanel/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap similarity index 99% rename from src/Explorer/Panes/DeleteCollectionConfirmationPanel/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap index 5999c9b22..8d5985440 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPanel/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = ` -
    - + `; diff --git a/src/Explorer/Panes/ExecuteSprocParamsPanel/index.test.tsx b/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.test.tsx similarity index 74% rename from src/Explorer/Panes/ExecuteSprocParamsPanel/index.test.tsx rename to src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.test.tsx index 56dbdb336..571ccb65c 100644 --- a/src/Explorer/Panes/ExecuteSprocParamsPanel/index.test.tsx +++ b/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.test.tsx @@ -2,7 +2,7 @@ import { mount } from "enzyme"; import React from "react"; import Explorer from "../../Explorer"; import StoredProcedure from "../../Tree/StoredProcedure"; -import { ExecuteSprocParamsPanel } from "./index"; +import { ExecuteSprocParamsPane } from "./ExecuteSprocParamsPane"; describe("Excute Sproc Param Pane", () => { const fakeExplorer = {} as Explorer; @@ -14,23 +14,23 @@ describe("Excute Sproc Param Pane", () => { }; it("should render Default properly", () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); it("initially display 2 input field, 1 partition and 1 parameter", () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find("input[type='text']")).toHaveLength(2); }); it("add a new parameter field", () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.find("#addparam").last().simulate("click"); expect(wrapper.find("input[type='text']")).toHaveLength(3); }); it("remove a parameter field", () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.find("#deleteparam").last().simulate("click"); expect(wrapper.find("input[type='text']")).toHaveLength(1); }); diff --git a/src/Explorer/Panes/ExecuteSprocParamsPanel/index.tsx b/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx similarity index 96% rename from src/Explorer/Panes/ExecuteSprocParamsPanel/index.tsx rename to src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx index cd8a3334c..ab63e4fcb 100644 --- a/src/Explorer/Panes/ExecuteSprocParamsPanel/index.tsx +++ b/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx @@ -4,7 +4,10 @@ import React, { FunctionComponent, useState } from "react"; import AddPropertyIcon from "../../../../images/Add-property.svg"; import Explorer from "../../Explorer"; import StoredProcedure from "../../Tree/StoredProcedure"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; import { InputParameter } from "./InputParameter"; interface ExecuteSprocParamsPaneProps { @@ -23,7 +26,7 @@ interface UnwrappedExecuteSprocParam { text: string; } -export const ExecuteSprocParamsPanel: FunctionComponent = ({ +export const ExecuteSprocParamsPane: FunctionComponent = ({ explorer, storedProcedure, closePanel, diff --git a/src/Explorer/Panes/ExecuteSprocParamsPanel/InputParameter.tsx b/src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx similarity index 100% rename from src/Explorer/Panes/ExecuteSprocParamsPanel/InputParameter.tsx rename to src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx diff --git a/src/Explorer/Panes/ExecuteSprocParamsPanel/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap similarity index 99% rename from src/Explorer/Panes/ExecuteSprocParamsPanel/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap index 9435e33e5..f0745e91f 100644 --- a/src/Explorer/Panes/ExecuteSprocParamsPanel/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Excute Sproc Param Pane should render Default properly 1`] = ` -
    - + `; diff --git a/src/Explorer/Panes/GenericRightPaneComponent/index.tsx b/src/Explorer/Panes/GenericRightPaneComponent/GenericRightPaneComponent.tsx similarity index 100% rename from src/Explorer/Panes/GenericRightPaneComponent/index.tsx rename to src/Explorer/Panes/GenericRightPaneComponent/GenericRightPaneComponent.tsx diff --git a/src/Explorer/Panes/LoadQueryPanel/index.test.tsx b/src/Explorer/Panes/LoadQueryPane/LoadQueryPane.test.tsx similarity index 77% rename from src/Explorer/Panes/LoadQueryPanel/index.test.tsx rename to src/Explorer/Panes/LoadQueryPane/LoadQueryPane.test.tsx index fbed0b672..71d1f7d3a 100644 --- a/src/Explorer/Panes/LoadQueryPanel/index.test.tsx +++ b/src/Explorer/Panes/LoadQueryPane/LoadQueryPane.test.tsx @@ -1,7 +1,7 @@ import { shallow } from "enzyme"; import React from "react"; import Explorer from "../../Explorer"; -import { LoadQueryPanel } from "./index"; +import { LoadQueryPane } from "./LoadQueryPane"; describe("Load Query Pane", () => { it("should render Default properly", () => { @@ -11,7 +11,7 @@ describe("Load Query Pane", () => { closePanel: (): void => undefined, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/src/Explorer/Panes/LoadQueryPanel/index.tsx b/src/Explorer/Panes/LoadQueryPane/LoadQueryPane.tsx similarity index 93% rename from src/Explorer/Panes/LoadQueryPanel/index.tsx rename to src/Explorer/Panes/LoadQueryPane/LoadQueryPane.tsx index 0b2816c04..6a8fed727 100644 --- a/src/Explorer/Panes/LoadQueryPanel/index.tsx +++ b/src/Explorer/Panes/LoadQueryPane/LoadQueryPane.tsx @@ -3,22 +3,25 @@ import { IImageProps, Image, ImageFit, Stack, TextField } from "office-ui-fabric import React, { FunctionComponent, useState } from "react"; import folderIcon from "../../../../images/folder_16x16.svg"; import { logError } from "../../../Common/Logger"; +import { Collection } from "../../../Contracts/ViewModels"; import { userContext } from "../../../UserContext"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; import QueryTab from "../../Tabs/QueryTab"; -import { Collection } from "..//../../Contracts/ViewModels"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; -interface LoadQueryPanelProps { +interface LoadQueryPaneProps { explorer: Explorer; closePanel: () => void; } -export const LoadQueryPanel: FunctionComponent = ({ +export const LoadQueryPane: FunctionComponent = ({ explorer, closePanel, -}: LoadQueryPanelProps): JSX.Element => { +}: LoadQueryPaneProps): JSX.Element => { const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false); const [formError, setFormError] = useState(""); const [formErrorsDetails, setFormErrorsDetails] = useState(""); diff --git a/src/Explorer/Panes/LoadQueryPanel/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/LoadQueryPane/__snapshots__/LoadQueryPane.test.tsx.snap similarity index 100% rename from src/Explorer/Panes/LoadQueryPanel/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/LoadQueryPane/__snapshots__/LoadQueryPane.test.tsx.snap diff --git a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx index e6028b29e..ee7927d69 100644 --- a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx +++ b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx @@ -1,20 +1,23 @@ +import { toJS } from "@nteract/commutable"; +import { ImmutableNotebook } from "@nteract/commutable/src"; import ko from "knockout"; import * as React from "react"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; -import Explorer from "../Explorer"; -import { JunoClient } from "../../Juno/JunoClient"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent"; -import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent"; -import { ImmutableNotebook } from "@nteract/commutable/src"; -import { toJS } from "@nteract/commutable"; -import { CodeOfConductComponent } from "../Controls/NotebookGallery/CodeOfConductComponent"; import { HttpStatusCodes } from "../../Common/Constants"; -import { handleError, getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; -import { GalleryTab } from "../Controls/NotebookGallery/GalleryViewerComponent"; -import { traceFailure, traceStart, traceSuccess } from "../../Shared/Telemetry/TelemetryProcessor"; +import { getErrorMessage, getErrorStack, handleError } from "../../Common/ErrorHandlingUtils"; +import { JunoClient } from "../../Juno/JunoClient"; import { Action } from "../../Shared/Telemetry/TelemetryConstants"; +import { traceFailure, traceStart, traceSuccess } from "../../Shared/Telemetry/TelemetryProcessor"; +import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { CodeOfConductComponent } from "../Controls/NotebookGallery/CodeOfConductComponent"; +import { GalleryTab } from "../Controls/NotebookGallery/GalleryViewerComponent"; +import Explorer from "../Explorer"; import * as FileSystemUtil from "../Notebook/FileSystemUtil"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "./GenericRightPaneComponent/GenericRightPaneComponent"; +import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent"; export class PublishNotebookPaneAdapter implements ReactAdapter { parameters: ko.Observable; diff --git a/src/Explorer/Panes/SaveQueryPanel/index.test.tsx b/src/Explorer/Panes/SaveQueryPane/SaveQueryPane.test.tsx similarity index 83% rename from src/Explorer/Panes/SaveQueryPanel/index.test.tsx rename to src/Explorer/Panes/SaveQueryPane/SaveQueryPane.test.tsx index 85e928b54..8edacc4a0 100644 --- a/src/Explorer/Panes/SaveQueryPanel/index.test.tsx +++ b/src/Explorer/Panes/SaveQueryPane/SaveQueryPane.test.tsx @@ -2,7 +2,7 @@ import { shallow } from "enzyme"; import * as ko from "knockout"; import React from "react"; import Explorer from "../../Explorer"; -import { SaveQueryPanel } from "./index"; +import { SaveQueryPane } from "./SaveQueryPane"; describe("Save Query Pane", () => { const fakeExplorer = {} as Explorer; @@ -13,7 +13,7 @@ describe("Save Query Pane", () => { closePanel: (): void => undefined, }; - const wrapper = shallow(); + const wrapper = shallow(); it("should return true if can save Queries else false", () => { fakeExplorer.canSaveQueries = ko.computed(() => true); @@ -26,7 +26,7 @@ describe("Save Query Pane", () => { }); it("should render Default properly", () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/src/Explorer/Panes/SaveQueryPanel/index.tsx b/src/Explorer/Panes/SaveQueryPane/SaveQueryPane.tsx similarity index 95% rename from src/Explorer/Panes/SaveQueryPanel/index.tsx rename to src/Explorer/Panes/SaveQueryPane/SaveQueryPane.tsx index a4e5bc559..949e9203f 100644 --- a/src/Explorer/Panes/SaveQueryPanel/index.tsx +++ b/src/Explorer/Panes/SaveQueryPane/SaveQueryPane.tsx @@ -9,17 +9,20 @@ import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetr import { logConsoleError } from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; import QueryTab from "../../Tabs/QueryTab"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; -interface SaveQueryPanelProps { +interface SaveQueryPaneProps { explorer: Explorer; closePanel: () => void; } -export const SaveQueryPanel: FunctionComponent = ({ +export const SaveQueryPane: FunctionComponent = ({ explorer, closePanel, -}: SaveQueryPanelProps): JSX.Element => { +}: SaveQueryPaneProps): JSX.Element => { const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false); const [formError, setFormError] = useState(""); const [formErrorsDetails, setFormErrorsDetails] = useState(""); diff --git a/src/Explorer/Panes/SaveQueryPanel/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/SaveQueryPane/__snapshots__/SaveQueryPane.test.tsx.snap similarity index 100% rename from src/Explorer/Panes/SaveQueryPanel/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/SaveQueryPane/__snapshots__/SaveQueryPane.test.tsx.snap diff --git a/src/Explorer/Panes/SettingsPane/index.test.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx similarity index 94% rename from src/Explorer/Panes/SettingsPane/index.test.tsx rename to src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx index a8959b2a3..6b601286c 100644 --- a/src/Explorer/Panes/SettingsPane/index.test.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx @@ -1,9 +1,9 @@ import { shallow } from "enzyme"; import React from "react"; -import { SettingsPane } from "."; import { DatabaseAccount } from "../../../Contracts/DataModels"; import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; +import { SettingsPane } from "./SettingsPane"; const props = { explorer: new Explorer(), closePanel: (): void => undefined, diff --git a/src/Explorer/Panes/SettingsPane/index.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx similarity index 98% rename from src/Explorer/Panes/SettingsPane/index.tsx rename to src/Explorer/Panes/SettingsPane/SettingsPane.tsx index fa561dd19..02dfdd614 100644 --- a/src/Explorer/Panes/SettingsPane/index.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -1,14 +1,17 @@ import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "office-ui-fabric-react"; import React, { FunctionComponent, MouseEvent, useState } from "react"; import * as Constants from "../../../Common/Constants"; -import { Tooltip } from "../../../Common/Tooltip"; +import { Tooltip } from "../../../Common/Tooltip/Tooltip"; import { configContext } from "../../../ConfigContext"; import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility"; import * as StringUtility from "../../../Shared/StringUtility"; import { userContext } from "../../../UserContext"; import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; export interface SettingsPaneProps { explorer: Explorer; diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap similarity index 100% rename from src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap diff --git a/src/Explorer/Panes/Tables/TableQuerySelectPanel/index.tsx b/src/Explorer/Panes/Tables/TableQuerySelectPanel/index.tsx index 76a54db6c..52a0b65d5 100644 --- a/src/Explorer/Panes/Tables/TableQuerySelectPanel/index.tsx +++ b/src/Explorer/Panes/Tables/TableQuerySelectPanel/index.tsx @@ -4,7 +4,10 @@ import { userContext } from "../../../../UserContext"; import Explorer from "../../../Explorer"; import * as Constants from "../../../Tables/Constants"; import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../../GenericRightPaneComponent/GenericRightPaneComponent"; interface TableQuerySelectPanelProps { explorer: Explorer; diff --git a/src/Explorer/Panes/UploadFilePane/index.tsx b/src/Explorer/Panes/UploadFilePane/UploadFilePane.tsx similarity index 95% rename from src/Explorer/Panes/UploadFilePane/index.tsx rename to src/Explorer/Panes/UploadFilePane/UploadFilePane.tsx index 68fd4b80e..70b66baea 100644 --- a/src/Explorer/Panes/UploadFilePane/index.tsx +++ b/src/Explorer/Panes/UploadFilePane/UploadFilePane.tsx @@ -1,9 +1,12 @@ import React, { ChangeEvent, FunctionComponent, useState } from "react"; -import { Upload } from "../../../Common/Upload"; +import { Upload } from "../../../Common/Upload/Upload"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; import { NotebookContentItem } from "../../Notebook/NotebookContentItem"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; export interface UploadFilePanelProps { explorer: Explorer; diff --git a/src/Explorer/Panes/UploadItemsPane/index.test.tsx b/src/Explorer/Panes/UploadItemsPane/UploadItemsPane.test.tsx similarity index 87% rename from src/Explorer/Panes/UploadItemsPane/index.test.tsx rename to src/Explorer/Panes/UploadItemsPane/UploadItemsPane.test.tsx index cf6bbd3e8..e259b6ba5 100644 --- a/src/Explorer/Panes/UploadItemsPane/index.test.tsx +++ b/src/Explorer/Panes/UploadItemsPane/UploadItemsPane.test.tsx @@ -1,7 +1,7 @@ import { shallow } from "enzyme"; import React from "react"; -import { UploadItemsPane } from "."; import Explorer from "../../Explorer"; +import { UploadItemsPane } from "./UploadItemsPane"; const props = { explorer: new Explorer(), closePanel: (): void => undefined, diff --git a/src/Explorer/Panes/UploadItemsPane/index.tsx b/src/Explorer/Panes/UploadItemsPane/UploadItemsPane.tsx similarity index 95% rename from src/Explorer/Panes/UploadItemsPane/index.tsx rename to src/Explorer/Panes/UploadItemsPane/UploadItemsPane.tsx index 0ac55ec45..c2836ba3c 100644 --- a/src/Explorer/Panes/UploadItemsPane/index.tsx +++ b/src/Explorer/Panes/UploadItemsPane/UploadItemsPane.tsx @@ -1,12 +1,15 @@ import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "office-ui-fabric-react"; import React, { ChangeEvent, FunctionComponent, useState } from "react"; -import { Upload } from "../../../Common/Upload"; +import { Upload } from "../../../Common/Upload/Upload"; import { UploadDetailsRecord } from "../../../Contracts/ViewModels"; import { userContext } from "../../../UserContext"; import { logConsoleError } from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; import { getErrorMessage } from "../../Tables/Utilities"; -import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; export interface UploadItemsPaneProps { explorer: Explorer; diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap similarity index 100% rename from src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap rename to src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap From e49bcc524fe67e841fceb0321fbf9c73c9667b7f Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Wed, 21 Apr 2021 11:52:01 -0700 Subject: [PATCH 15/23] Remove deprecated calls to logConsoleMessage (#608) Co-authored-by: Steve Faulkner --- src/Common/MongoProxyClient.ts | 10 +- src/Explorer/DataSamples/DataSamplesUtil.ts | 11 +-- src/Explorer/Explorer.tsx | 62 ++++--------- .../GraphExplorerComponent/D3ForceGraph.ts | 36 ++++--- .../GraphExplorerComponent/GraphExplorer.tsx | 93 +++++++++++-------- .../GraphExplorerComponent/GremlinClient.ts | 11 +-- .../Notebook/NotebookComponent/epics.ts | 91 +++++++++--------- .../Notebook/NotebookContainerClient.ts | 20 ++-- src/Explorer/Panes/StringInputPane.ts | 20 +--- src/Explorer/Tables/TableDataClient.ts | 92 ++++++------------ src/Explorer/Tabs/DocumentsTab.ts | 5 +- src/Explorer/Tabs/MongoShellTab.ts | 11 +-- src/Explorer/Tabs/NotebookV2Tab.ts | 9 +- src/Explorer/Tree/Database.ts | 8 +- src/GitHub/GitHubOAuthService.ts | 9 +- src/Utils/GalleryUtils.ts | 52 ++++------- src/Utils/NotificationConsoleUtils.ts | 82 ++++------------ 17 files changed, 234 insertions(+), 388 deletions(-) diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 0680a26c2..ad22c9257 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -5,11 +5,10 @@ import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; import { MessageTypes } from "../Contracts/ExplorerContracts"; import { Collection } from "../Contracts/ViewModels"; -import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import DocumentId from "../Explorer/Tree/DocumentId"; -import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; -import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; import { userContext } from "../UserContext"; +import { logConsoleError } from "../Utils/NotificationConsoleUtils"; +import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; import { MinimalQueryIterator } from "./IteratorUtilities"; import { sendMessage } from "./MessageHandler"; @@ -348,10 +347,7 @@ export function getEndpoint(): string { async function errorHandling(response: Response, action: string, params: unknown): Promise { const errorMessage = await response.text(); // Log the error where the user can see it - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}` - ); + logConsoleError(`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`); if (response.status === HttpStatusCodes.Forbidden) { sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage }); return; diff --git a/src/Explorer/DataSamples/DataSamplesUtil.ts b/src/Explorer/DataSamples/DataSamplesUtil.ts index 809964352..63b35cfff 100644 --- a/src/Explorer/DataSamples/DataSamplesUtil.ts +++ b/src/Explorer/DataSamples/DataSamplesUtil.ts @@ -1,8 +1,7 @@ import * as ViewModels from "../../Contracts/ViewModels"; import { userContext } from "../../UserContext"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import Explorer from "../Explorer"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; export class DataSamplesUtil { @@ -21,18 +20,16 @@ export class DataSamplesUtil { if (this.hasContainer(databaseName, containerName, this.container.databases())) { const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`; this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); return; } await generator .createSampleContainerAsync() - .catch((error) => - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Error creating sample container: ${error}`) - ); + .catch((error) => logConsoleError(`Error creating sample container: ${error}`)); const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`; this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); + logConsoleInfo(msg); } /** diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 01a746934..e79d99c99 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -36,6 +36,7 @@ import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationU import { stringToBlob } from "../Utils/BlobUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; +import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import * as PricingUtils from "../Utils/PricingUtils"; import * as ComponentRegisterer from "./ComponentRegisterer"; import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker"; @@ -43,7 +44,7 @@ import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandBut import { DialogProps, TextFieldProps } from "./Controls/Dialog"; import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; -import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; +import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookUtil } from "./Notebook/NotebookUtil"; @@ -736,8 +737,7 @@ export default class Explorer { onPrimaryButtonClick: async () => { const startTime = TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink); - const logId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, + const clearInProgressMessage = logConsoleProgress( "Enabling Azure Synapse Link for this account. This may take a few minutes before you can enable analytical store for this account." ); this.isSynapseLinkUpdating(true); @@ -755,19 +755,13 @@ export default class Explorer { }, } ); - NotificationConsoleUtils.clearInProgressMessageWithId(logId); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - "Enabled Azure Synapse Link for this account" - ); + clearInProgressMessage(); + logConsoleInfo("Enabled Azure Synapse Link for this account"); TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, {}, startTime); this.databaseAccount(databaseAccount); } catch (error) { - NotificationConsoleUtils.clearInProgressMessageWithId(logId); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}` - ); + clearInProgressMessage(); + logConsoleError(`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`); TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, {}, startTime); } finally { this.isSynapseLinkUpdating(false); @@ -894,10 +888,7 @@ export default class Explorer { }, startKey ); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error while refreshing databases: ${errorMessage}` - ); + logConsoleError(`Error while refreshing databases: ${errorMessage}`); } ); @@ -1118,20 +1109,20 @@ export default class Explorer { private _resetNotebookWorkspace = async () => { this._closeModalDialog(); - const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Resetting notebook workspace"); + const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace"); try { await this.notebookManager?.notebookClient.resetWorkspace(); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully reset notebook workspace"); + logConsoleInfo("Successfully reset notebook workspace"); TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace); } catch (error) { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to reset notebook workspace: ${error}`); + logConsoleError(`Failed to reset notebook workspace: ${error}`); TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, { error: getErrorMessage(error), errorStack: getErrorStack(error), }); throw error; } finally { - NotificationConsoleUtils.clearInProgressMessageWithId(id); + clearInProgressMessage(); } }; @@ -1688,11 +1679,7 @@ export default class Explorer { clearMessage(); }, (error: any) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Could not download notebook ${getErrorMessage(error)}` - ); - + logConsoleError(`Could not download notebook ${getErrorMessage(error)}`); clearMessage(); } ); @@ -1844,15 +1831,8 @@ export default class Explorer { } return this.notebookManager?.notebookContentClient.deleteContentItem(item).then( - () => { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully deleted: ${item.path}`); - }, - (reason: any) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Failed to delete "${item.path}": ${JSON.stringify(reason)}` - ); - } + () => logConsoleInfo(`Successfully deleted: ${item.path}`), + (reason: any) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`) ); } @@ -1868,11 +1848,7 @@ export default class Explorer { parent = parent || this.resourceTree.myNotebooksContentRoot; - const notificationProgressId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Creating new notebook in ${parent.path}` - ); - + const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`); const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, { dataExplorerArea: Constants.Areas.Notebook, }); @@ -1880,7 +1856,7 @@ export default class Explorer { this.notebookManager?.notebookContentClient .createNewNotebookFile(parent) .then((newFile: NotebookContentItem) => { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully created: ${newFile.name}`); + logConsoleInfo(`Successfully created: ${newFile.name}`); TelemetryProcessor.traceSuccess( Action.CreateNewNotebook, { @@ -1893,7 +1869,7 @@ export default class Explorer { .then(() => this.resourceTree.triggerRender()) .catch((error: any) => { const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage); + logConsoleError(errorMessage); TelemetryProcessor.traceFailure( Action.CreateNewNotebook, { @@ -1904,7 +1880,7 @@ export default class Explorer { startKey ); }) - .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(notificationProgressId)); + .finally(clearInProgressMessage); } public refreshContentItem(item: NotebookContentItem): Promise { diff --git a/src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.ts b/src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.ts index efe403eec..31a01f0a3 100644 --- a/src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.ts +++ b/src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.ts @@ -1,24 +1,22 @@ -import * as ko from "knockout"; -import Q from "q"; -import { schemeCategory10 } from "d3-scale-chromatic"; -import { selectAll, select } from "d3-selection"; -import { zoom, zoomIdentity } from "d3-zoom"; -import { scaleOrdinal } from "d3-scale"; -import { forceSimulation, forceLink, forceCollide, forceManyBody } from "d3-force"; -import { interpolateNumber, interpolate } from "d3-interpolate"; +import { BaseType } from "d3"; import { map as d3Map } from "d3-collection"; -import { drag, D3DragEvent } from "d3-drag"; - +import { D3DragEvent, drag } from "d3-drag"; +import { forceCollide, forceLink, forceManyBody, forceSimulation } from "d3-force"; +import { interpolate, interpolateNumber } from "d3-interpolate"; +import { scaleOrdinal } from "d3-scale"; +import { schemeCategory10 } from "d3-scale-chromatic"; +import { select, selectAll } from "d3-selection"; +import { zoom, zoomIdentity } from "d3-zoom"; +import * as ko from "knockout"; +import Q from "q"; import _ from "underscore"; -import { NeighborType } from "../../../Contracts/ViewModels"; -import { GraphData, D3Node, D3Link } from "./GraphData"; -import { HashMap } from "../../../Common/HashMap"; -import { BaseType } from "d3"; -import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import { GraphConfig } from "../../Tabs/GraphTab"; -import { GraphExplorer } from "./GraphExplorer"; import * as Constants from "../../../Common/Constants"; +import { HashMap } from "../../../Common/HashMap"; +import { NeighborType } from "../../../Contracts/ViewModels"; +import { logConsoleError } from "../../../Utils/NotificationConsoleUtils"; +import { GraphConfig } from "../../Tabs/GraphTab"; +import { D3Link, D3Node, GraphData } from "./GraphData"; +import { GraphExplorer } from "./GraphExplorer"; export interface D3GraphIconMap { [key: string]: { data: string; format: string }; @@ -1005,7 +1003,7 @@ export class D3ForceGraph implements GraphRenderer { */ private loadNeighbors(v: D3Node, pageAction: PAGE_ACTION) { if (!this.graphDataWrapper.hasVertexId(v.id)) { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Clicked node not in graph data. id: ${v.id}`); + logConsoleError(`Clicked node not in graph data. id: ${v.id}`); return; } diff --git a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx index ba35d2fd8..bfd23f09c 100644 --- a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx +++ b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx @@ -1,37 +1,36 @@ +import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import * as Q from "q"; import * as React from "react"; -import * as LeftPane from "./LeftPaneComponent"; -import { MiddlePaneComponent } from "./MiddlePaneComponent"; -import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent"; -import * as NodeProperties from "./NodePropertiesComponent"; -import * as D3ForceGraph from "./D3ForceGraph"; -import { GraphVizComponentProps } from "./GraphVizComponent"; -import * as GraphData from "./GraphData"; -import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import * as GraphUtil from "./GraphUtil"; -import * as DataModels from "../../../Contracts/DataModels"; -import * as ViewModels from "../../../Contracts/ViewModels"; -import * as GremlinClient from "./GremlinClient"; -import * as StorageUtility from "../../../Shared/StorageUtility"; -import { ArraysByKeyCache } from "./ArraysByKeyCache"; -import { EdgeInfoCache } from "./EdgeInfoCache"; -import * as TabComponent from "../../Controls/Tabs/TabComponent"; -import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility"; -import { QueryContainerComponent } from "./QueryContainerComponent"; -import { GraphConfig } from "../../Tabs/GraphTab"; -import { EditorReact } from "../../Controls/Editor/EditorReact"; import LoadGraphIcon from "../../../../images/LoadGraph.png"; -import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; -import * as Constants from "../../../Common/Constants"; -import { InputProperty } from "../../../Contracts/ViewModels"; -import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos"; import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif"; +import * as Constants from "../../../Common/Constants"; import { queryDocuments } from "../../../Common/dataAccess/queryDocuments"; import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage"; import { getErrorMessage } from "../../../Common/ErrorHandlingUtils"; -import { FeedOptions } from "@azure/cosmos"; +import * as DataModels from "../../../Contracts/DataModels"; +import * as ViewModels from "../../../Contracts/ViewModels"; +import { InputProperty } from "../../../Contracts/ViewModels"; +import * as StorageUtility from "../../../Shared/StorageUtility"; +import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility"; +import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; +import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; +import { EditorReact } from "../../Controls/Editor/EditorReact"; +import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent"; +import * as TabComponent from "../../Controls/Tabs/TabComponent"; +import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; +import { GraphConfig } from "../../Tabs/GraphTab"; +import { ArraysByKeyCache } from "./ArraysByKeyCache"; +import * as D3ForceGraph from "./D3ForceGraph"; +import { EdgeInfoCache } from "./EdgeInfoCache"; +import * as GraphData from "./GraphData"; +import * as GraphUtil from "./GraphUtil"; +import { GraphVizComponentProps } from "./GraphVizComponent"; +import * as GremlinClient from "./GremlinClient"; +import * as LeftPane from "./LeftPaneComponent"; +import { MiddlePaneComponent } from "./MiddlePaneComponent"; +import * as NodeProperties from "./NodePropertiesComponent"; +import { QueryContainerComponent } from "./QueryContainerComponent"; export interface GraphAccessor { applyFilter: () => void; @@ -697,13 +696,13 @@ export class GraphExplorer extends React.Component { - const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${cmd}`); + const clearConsoleProgress = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${cmd}`); this.setExecuteCounter(this.executeCounter + 1); return this.gremlinClient.execute(cmd).then( (result: GremlinClient.GremlinRequestResult) => { this.setExecuteCounter(this.executeCounter - 1); - GraphExplorer.clearConsoleProgress(id); + clearConsoleProgress(); if (result.isIncomplete) { const msg = `The query results are too large and only partial results are displayed for: ${cmd}`; GraphExplorer.reportToConsole(ConsoleDataType.Error, msg); @@ -718,7 +717,7 @@ export class GraphExplorer extends React.Component { this.setExecuteCounter(this.executeCounter - 1); GraphExplorer.reportToConsole(ConsoleDataType.Error, `Gremlin query failed: ${cmd}`, err); - GraphExplorer.clearConsoleProgress(id); + clearConsoleProgress(); throw err; } ); @@ -1083,13 +1082,26 @@ export class GraphExplorer extends React.Component void; + public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void; + public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void; + public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) { let errorDataStr: string = ""; if (errorData && errorData.length > 0) { console.error(msg, errorData); errorDataStr = ": " + JSON.stringify(errorData); } - return NotificationConsoleUtils.logConsoleMessage(type, `${msg}${errorDataStr}`); + + const consoleMessage = `${msg}${errorDataStr}`; + + switch (type) { + case ConsoleDataType.Error: + return logConsoleError(consoleMessage); + case ConsoleDataType.Info: + return logConsoleInfo(consoleMessage); + case ConsoleDataType.InProgress: + return logConsoleProgress(consoleMessage); + } } private setNodePropertiesViewMode(viewMode: NodeProperties.Mode) { @@ -1368,7 +1380,7 @@ export class GraphExplorer extends React.Component ); } - - private static clearConsoleProgress(id: string) { - NotificationConsoleUtils.clearInProgressMessageWithId(id); - } } diff --git a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts index 6daf7ea23..ee022b873 100644 --- a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts +++ b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts @@ -3,11 +3,10 @@ */ import * as Q from "q"; -import { GremlinSimpleClient, Result } from "./GremlinSimpleClient"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; -import { HashMap } from "../../../Common/HashMap"; import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; +import { HashMap } from "../../../Common/HashMap"; +import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils"; +import { GremlinSimpleClient, Result } from "./GremlinSimpleClient"; export interface GremlinClientParameters { endpoint: string; @@ -77,9 +76,7 @@ export class GremlinClient { this.abortPendingRequest(requestId, errorMessage, result.requestCharge); } }, - infoCallback: (msg: string) => { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); - }, + infoCallback: logConsoleInfo, }); } diff --git a/src/Explorer/Notebook/NotebookComponent/epics.ts b/src/Explorer/Notebook/NotebookComponent/epics.ts index 544f80bf4..8c8610c0a 100644 --- a/src/Explorer/Notebook/NotebookComponent/epics.ts +++ b/src/Explorer/Notebook/NotebookComponent/epics.ts @@ -1,52 +1,49 @@ -import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer, from } from "rxjs"; -import { webSocket } from "rxjs/webSocket"; -import { StateObservable } from "redux-observable"; -import { ofType } from "redux-observable"; import { - mergeMap, - tap, - retryWhen, - delayWhen, - map, - switchMap, - take, - filter, - catchError, - first, - concatMap, - timeout, -} from "rxjs/operators"; -import { - AppState, - ServerConfig as JupyterServerConfig, - JupyterHostRecordProps, - RemoteKernelProps, - castToSessionId, - createKernelRef, - KernelRef, - ContentRef, - KernelInfo, actions, + AppState, + castToSessionId, + ContentRef, + createKernelRef, + JupyterHostRecordProps, + KernelInfo, + KernelRef, + RemoteKernelProps, selectors, + ServerConfig as JupyterServerConfig, } from "@nteract/core"; -import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging"; -import { sessions, kernels } from "rx-jupyter"; +import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging"; import { RecordOf } from "immutable"; import { AnyAction } from "redux"; - +import { ofType, StateObservable } from "redux-observable"; +import { kernels, sessions } from "rx-jupyter"; +import { concat, EMPTY, from, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs"; +import { + catchError, + concatMap, + delayWhen, + filter, + first, + map, + mergeMap, + retryWhen, + switchMap, + take, + tap, + timeout, +} from "rxjs/operators"; +import { webSocket } from "rxjs/webSocket"; import * as Constants from "../../../Common/Constants"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as CdbActions from "./actions"; -import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; +import { Areas } from "../../../Common/Constants"; import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; -import { CdbAppState } from "./types"; +import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import { decryptJWTToken } from "../../../Utils/AuthorizationUtils"; -import * as TextFile from "./contents/file/text-file"; -import { NotebookUtil } from "../NotebookUtil"; +import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils"; import * as FileSystemUtil from "../FileSystemUtil"; import * as cdbActions from "../NotebookComponent/actions"; -import { Areas } from "../../../Common/Constants"; +import { NotebookUtil } from "../NotebookUtil"; +import * as CdbActions from "./actions"; +import * as TextFile from "./contents/file/text-file"; +import { CdbAppState } from "./types"; interface NotebookServiceConfig extends JupyterServerConfig { userPuid?: string; @@ -311,7 +308,7 @@ export const launchWebSocketKernelEpic = ( if (currentKernelspecs) { kernelSpecToLaunch = currentKernelspecs.defaultKernelName; const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); + logConsoleInfo(msg); logFailureToTelemetry(state$.value, "Launching alternate kernel", msg); } else { return of( @@ -337,7 +334,7 @@ export const launchWebSocketKernelEpic = ( kernelSpecToLaunch = currentKernelspecs.defaultKernelName; msg += ` Using default kernel: ${kernelSpecToLaunch}`; } - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); + logConsoleInfo(msg); logFailureToTelemetry(state$.value, "Launching alternate kernel", msg); } @@ -634,7 +631,7 @@ const notificationsToUserEpic = (action$: Observable, state$: StateObservab case actions.RESTART_KERNEL_SUCCESSFUL: { const title = "Kernel restart"; const msg = "Kernel successfully restarted"; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); + logConsoleInfo(msg); logFailureToTelemetry(state$.value, title, msg); break; } @@ -645,7 +642,7 @@ const notificationsToUserEpic = (action$: Observable, state$: StateObservab case actions.SAVE_FAILED: { const title = "Save failure"; const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); logFailureToTelemetry(state$.value, title, msg); break; } @@ -654,7 +651,7 @@ const notificationsToUserEpic = (action$: Observable, state$: StateObservab const filepath = selectors.filepath(state$.value, { contentRef: typedAction.payload.contentRef }); const title = "Fetching content failure"; const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); logFailureToTelemetry(state$.value, title, msg); break; } @@ -679,7 +676,7 @@ const handleKernelConnectionLostEpic = ( const state = state$.value; const msg = "Notebook was disconnected from kernel"; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); logFailureToTelemetry(state, "Error", "Kernel connection error"); const host = selectors.currentHost(state); @@ -692,7 +689,7 @@ const handleKernelConnectionLostEpic = ( if (delayMs > Constants.Notebook.kernelRestartMaxDelayMs) { const msg = "Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically."; - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); logFailureToTelemetry(state, "Kernel restart error", msg); const explorer = window.dataExplorer; @@ -810,7 +807,7 @@ const closeUnsupportedMimetypesEpic = ( ); const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`; explorer.showOkModalDialog("File cannot be rendered", msg); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); } return EMPTY; }) @@ -838,7 +835,7 @@ const closeContentFailedToFetchEpic = ( ); const msg = `Failed to load file: ${filepath}.`; explorer.showOkModalDialog("Failure to load", msg); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); + logConsoleError(msg); } return EMPTY; }) diff --git a/src/Explorer/Notebook/NotebookContainerClient.ts b/src/Explorer/Notebook/NotebookContainerClient.ts index 7d5a82318..33863c8cb 100644 --- a/src/Explorer/Notebook/NotebookContainerClient.ts +++ b/src/Explorer/Notebook/NotebookContainerClient.ts @@ -1,15 +1,14 @@ /** * Notebook container related stuff */ -import * as DataModels from "../../Contracts/DataModels"; import * as Constants from "../../Common/Constants"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import * as Logger from "../../Common/Logger"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; +import * as Logger from "../../Common/Logger"; +import * as DataModels from "../../Contracts/DataModels"; +import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; export class NotebookContainerClient { - private reconnectingNotificationId: string; + private clearReconnectionAttemptMessage? = () => {}; private isResettingWorkspace: boolean; constructor( @@ -61,9 +60,9 @@ export class NotebookContainerClient { }, }); if (response.ok) { - if (this.reconnectingNotificationId) { - NotificationConsoleUtils.clearInProgressMessageWithId(this.reconnectingNotificationId); - this.reconnectingNotificationId = ""; + if (this.clearReconnectionAttemptMessage) { + this.clearReconnectionAttemptMessage(); + this.clearReconnectionAttemptMessage = undefined; } const memoryUsageInfo = await response.json(); if (memoryUsageInfo) { @@ -76,9 +75,8 @@ export class NotebookContainerClient { return undefined; } catch (error) { Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage"); - if (!this.reconnectingNotificationId) { - this.reconnectingNotificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, + if (!this.clearReconnectionAttemptMessage) { + this.clearReconnectionAttemptMessage = logConsoleProgress( "Connection lost with Notebook server. Attempting to reconnect..." ); } diff --git a/src/Explorer/Panes/StringInputPane.ts b/src/Explorer/Panes/StringInputPane.ts index 5679fd115..0c28ce98b 100644 --- a/src/Explorer/Panes/StringInputPane.ts +++ b/src/Explorer/Panes/StringInputPane.ts @@ -1,9 +1,8 @@ import * as ko from "knockout"; import Q from "q"; import * as ViewModels from "../../Contracts/ViewModels"; +import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { ContextualPaneBase } from "./ContextualPaneBase"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; export interface StringInputPaneOpenOptions { paneTitle: string; @@ -39,19 +38,13 @@ export class StringInputPane extends ContextualPaneBase { this.formErrors(""); this.formErrorsDetails(""); - const id: string = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `${this.openOptions.inProgressMessage} ${this.stringInput()}` - ); + const clearInProgressMessage = logConsoleProgress(`${this.openOptions.inProgressMessage} ${this.stringInput()}`); this.isExecuting(true); this.openOptions .onSubmit(this.stringInput()) .then( (value: any) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `${this.openOptions.successMessage}: ${this.stringInput()}` - ); + logConsoleInfo(`${this.openOptions.successMessage}: ${this.stringInput()}`); this.close(); this.paneDeferred.resolve(value); }, @@ -70,16 +63,13 @@ export class StringInputPane extends ContextualPaneBase { this.formErrors(this.openOptions.errorMessage); this.formErrorsDetails(`${this.openOptions.errorMessage}: ${error}`); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `${this.openOptions.errorMessage} ${this.stringInput()}: ${error}` - ); + logConsoleError(`${this.openOptions.errorMessage} ${this.stringInput()}: ${error}`); this.paneDeferred.reject(error); } ) .finally(() => { this.isExecuting(false); - NotificationConsoleUtils.clearInProgressMessageWithId(id); + clearInProgressMessage(); }); } diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 7603fc89e..b97acc4b1 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -1,25 +1,24 @@ +import { FeedOptions } from "@azure/cosmos"; import * as ko from "knockout"; import Q from "q"; - -import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; import { AuthType } from "../../AuthType"; -import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; -import { FeedOptions } from "@azure/cosmos"; import * as Constants from "../../Common/Constants"; -import * as Entities from "./Entities"; -import * as HeadersUtility from "../../Common/HeadersUtility"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import * as TableConstants from "./Constants"; -import * as TableEntityProcessor from "./TableEntityProcessor"; -import * as ViewModels from "../../Contracts/ViewModels"; -import Explorer from "../Explorer"; -import { configContext } from "../../ConfigContext"; -import { handleError } from "../../Common/ErrorHandlingUtils"; import { createDocument } from "../../Common/dataAccess/createDocument"; import { deleteDocument } from "../../Common/dataAccess/deleteDocument"; import { queryDocuments } from "../../Common/dataAccess/queryDocuments"; import { updateDocument } from "../../Common/dataAccess/updateDocument"; +import { handleError } from "../../Common/ErrorHandlingUtils"; +import * as HeadersUtility from "../../Common/HeadersUtility"; +import { configContext } from "../../ConfigContext"; +import * as ViewModels from "../../Contracts/ViewModels"; import { userContext } from "../../UserContext"; +import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; +import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; +import Explorer from "../Explorer"; +import * as TableConstants from "./Constants"; +import * as Entities from "./Entities"; +import * as TableEntityProcessor from "./TableEntityProcessor"; export interface CassandraTableKeys { partitionKeys: CassandraTableKey[]; @@ -144,10 +143,7 @@ export class CassandraAPIDataClient extends TableDataClient { collection: ViewModels.Collection, entity: Entities.ITableEntity ): Q.Promise { - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Adding new row to table ${collection.id()}` - ); + const clearInProgressMessage = logConsoleProgress(`Adding new row to table ${collection.id()}`); let properties = "("; let values = "("; for (let property in entity) { @@ -171,7 +167,7 @@ export class CassandraAPIDataClient extends TableDataClient { (data: any) => { entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)]; entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString(); - NotificationConsoleUtils.logConsoleInfo(`Successfully added new row to table ${collection.id()}`); + logConsoleInfo(`Successfully added new row to table ${collection.id()}`); deferred.resolve(entity); }, (error) => { @@ -179,9 +175,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.reject(error); } ) - .finally(() => { - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); - }); + .finally(clearInProgressMessage); return deferred.promise; } @@ -341,17 +335,11 @@ export class CassandraAPIDataClient extends TableDataClient { } const deferred: Q.Deferred = Q.defer(); - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Creating a new keyspace with query ${createKeyspaceQuery}` - ); + const clearInProgressMessage = logConsoleProgress(`Creating a new keyspace with query ${createKeyspaceQuery}`); this.createOrDeleteQuery(cassandraEndpoint, resourceId, createKeyspaceQuery) .then( (data: any) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `Successfully created a keyspace with query ${createKeyspaceQuery}` - ); + logConsoleInfo(`Successfully created a keyspace with query ${createKeyspaceQuery}`); deferred.resolve(); }, (error) => { @@ -363,9 +351,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.reject(error); } ) - .finally(() => { - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); - }); + .finally(clearInProgressMessage); return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs); } @@ -387,17 +373,11 @@ export class CassandraAPIDataClient extends TableDataClient { const deferred = Q.defer(); createKeyspacePromise.then( () => { - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Creating a new table with query ${createTableQuery}` - ); + const clearInProgressMessage = logConsoleProgress(`Creating a new table with query ${createTableQuery}`); this.createOrDeleteQuery(cassandraEndpoint, resourceId, createTableQuery) .then( (data: any) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `Successfully created a table with query ${createTableQuery}` - ); + logConsoleInfo(`Successfully created a table with query ${createTableQuery}`); deferred.resolve(); }, (error) => { @@ -405,9 +385,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.reject(error); } ) - .finally(() => { - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); - }); + .finally(clearInProgressMessage); }, (reason) => { deferred.reject(reason); @@ -420,10 +398,7 @@ export class CassandraAPIDataClient extends TableDataClient { if (!!collection.cassandraKeys) { return Q.resolve(collection.cassandraKeys); } - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Fetching keys for table ${collection.id()}` - ); + const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`); const authType = userContext.authType; const apiEndpoint: string = authType === AuthType.EncryptedToken @@ -448,10 +423,7 @@ export class CassandraAPIDataClient extends TableDataClient { .then( (data: CassandraTableKeys) => { collection.cassandraKeys = data; - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `Successfully fetched keys for table ${collection.id()}` - ); + logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`); deferred.resolve(data); }, (error: any) => { @@ -459,9 +431,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.reject(error); } ) - .done(() => { - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); - }); + .done(clearInProgressMessage); return deferred.promise; } @@ -469,10 +439,7 @@ export class CassandraAPIDataClient extends TableDataClient { if (!!collection.cassandraSchema) { return Q.resolve(collection.cassandraSchema); } - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Fetching schema for table ${collection.id()}` - ); + const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`); const authType = userContext.authType; const apiEndpoint: string = authType === AuthType.EncryptedToken @@ -497,10 +464,7 @@ export class CassandraAPIDataClient extends TableDataClient { .then( (data: any) => { collection.cassandraSchema = data.columns; - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `Successfully fetched schema for table ${collection.id()}` - ); + logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`); deferred.resolve(data.columns); }, (error: any) => { @@ -508,9 +472,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.reject(error); } ) - .done(() => { - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); - }); + .done(clearInProgressMessage); return deferred.promise; } diff --git a/src/Explorer/Tabs/DocumentsTab.ts b/src/Explorer/Tabs/DocumentsTab.ts index d53b02e17..cdb1f739e 100644 --- a/src/Explorer/Tabs/DocumentsTab.ts +++ b/src/Explorer/Tabs/DocumentsTab.ts @@ -21,11 +21,10 @@ import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { logConsoleError } from "../../Utils/NotificationConsoleUtils"; import * as QueryUtils from "../../Utils/QueryUtils"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import Explorer from "../Explorer"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList"; import DocumentId from "../Tree/DocumentId"; import template from "./DocumentsTab.html"; @@ -727,7 +726,7 @@ export default class DocumentsTab extends TabsBase { (error) => { this.isExecutionError(true); const errorMessage = getErrorMessage(error); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage); + logConsoleError(errorMessage); if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) { TelemetryProcessor.traceFailure( Action.Tab, diff --git a/src/Explorer/Tabs/MongoShellTab.ts b/src/Explorer/Tabs/MongoShellTab.ts index 9612a0219..4dbdc0c99 100644 --- a/src/Explorer/Tabs/MongoShellTab.ts +++ b/src/Explorer/Tabs/MongoShellTab.ts @@ -7,9 +7,8 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { isInvalidParentFrameOrigin, isReadyMessage } from "../../Utils/MessageValidation"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import Explorer from "../Explorer"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import template from "./MongoShellTab.html"; import TabsBase from "./TabsBase"; @@ -184,13 +183,11 @@ export default class MongoShellTab extends TabsBase { switch (logType) { case LogType.Information: - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, dataToLog); - break; + return logConsoleInfo(dataToLog); case LogType.Warning: - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, dataToLog); - break; + return logConsoleError(dataToLog); case LogType.InProgress: - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, dataToLog); + return logConsoleProgress(dataToLog); } } } diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index c3f3817be..168166ef7 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -21,11 +21,10 @@ import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/Telemetr import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import Explorer from "../Explorer"; import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { KernelSpecsDisplay, NotebookClientV2 } from "../Notebook/NotebookClientV2"; import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; import { NotebookContentItem } from "../Notebook/NotebookContentItem"; @@ -62,11 +61,7 @@ export default class NotebookTabV2 extends TabsBase { } this.notebookPath = ko.observable(options.notebookContentItem.path); - - this.container.notebookServerInfo.subscribe((newValue: DataModels.NotebookWorkspaceConnectionInfo) => { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "New notebook server info received."); - }); - + this.container.notebookServerInfo.subscribe(() => logConsoleInfo("New notebook server info received.")); this.notebookComponentAdapter = new NotebookComponentAdapter({ contentItem: options.notebookContentItem, notebooksBasePath: this.container.getNotebookBasePath(), diff --git a/src/Explorer/Tree/Database.ts b/src/Explorer/Tree/Database.ts index cae0273e0..c82cd686e 100644 --- a/src/Explorer/Tree/Database.ts +++ b/src/Explorer/Tree/Database.ts @@ -13,9 +13,8 @@ import { IJunoResponse, JunoClient } from "../../Juno/JunoClient"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { logConsoleError } from "../../Utils/NotificationConsoleUtils"; import Explorer from "../Explorer"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2"; import Collection from "./Collection"; @@ -101,10 +100,7 @@ export default class Database implements ViewModels.Database { }, startKey ); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error while fetching database settings for database ${this.id()}: ${errorMessage}` - ); + logConsoleError(`Error while fetching database settings for database ${this.id()}: ${errorMessage}`); throw error; } ); diff --git a/src/GitHub/GitHubOAuthService.ts b/src/GitHub/GitHubOAuthService.ts index 15d0c2d38..779d70bd7 100644 --- a/src/GitHub/GitHubOAuthService.ts +++ b/src/GitHub/GitHubOAuthService.ts @@ -1,13 +1,12 @@ import ko from "knockout"; import { HttpStatusCodes } from "../Common/Constants"; +import { handleError } from "../Common/ErrorHandlingUtils"; import { configContext } from "../ConfigContext"; import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent"; -import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { JunoClient } from "../Juno/JunoClient"; import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation"; -import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; +import { logConsoleInfo } from "../Utils/NotificationConsoleUtils"; import { GitHubConnectorMsgType, IGitHubConnectorParams } from "./GitHubConnector"; -import { handleError } from "../Common/ErrorHandlingUtils"; window.addEventListener("message", (event: MessageEvent) => { if (isInvalidParentFrameOrigin(event)) { @@ -70,7 +69,7 @@ export class GitHubOAuthService { const response = await this.junoClient.getGitHubToken(params.code); if (response.status === HttpStatusCodes.OK && !response.data.error) { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully connected to GitHub"); + logConsoleInfo("Successfully connected to GitHub"); this.token(response.data); } else { let errorMsg = response.data.error; @@ -80,7 +79,7 @@ export class GitHubOAuthService { throw new Error(errorMsg); } } catch (error) { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect to GitHub: ${error}`); + logConsoleInfo(`Failed to connect to GitHub: ${error}`); this.token({ error }); } } diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts index 78a4c84b2..02398bae5 100644 --- a/src/Utils/GalleryUtils.ts +++ b/src/Utils/GalleryUtils.ts @@ -1,20 +1,19 @@ -import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; -import * as NotificationConsoleUtils from "./NotificationConsoleUtils"; -import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; -import { - GalleryTab, - SortBy, - GalleryViewerComponent, -} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; -import Explorer from "../Explorer/Explorer"; -import { IChoiceGroupOption, IChoiceGroupProps, IProgressIndicatorProps } from "office-ui-fabric-react"; -import { TextFieldProps } from "../Explorer/Controls/Dialog"; -import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; -import { HttpStatusCodes } from "../Common/Constants"; -import { trace, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor"; -import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Notebook } from "@nteract/commutable"; import { NotebookV4 } from "@nteract/commutable/lib/v4"; +import { IChoiceGroupOption, IChoiceGroupProps, IProgressIndicatorProps } from "office-ui-fabric-react"; +import { HttpStatusCodes } from "../Common/Constants"; +import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; +import { TextFieldProps } from "../Explorer/Controls/Dialog"; +import { + GalleryTab, + GalleryViewerComponent, + SortBy, +} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; +import Explorer from "../Explorer/Explorer"; +import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; +import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; +import { trace, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor"; +import { logConsoleInfo, logConsoleProgress } from "./NotificationConsoleUtils"; const defaultSelectedAbuseCategory = "Other"; const abuseCategories: IChoiceGroupOption[] = [ @@ -228,11 +227,7 @@ export function downloadItem( `Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`, "Download", async () => { - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Downloading ${name} to My Notebooks` - ); - + const clearInProgressMessage = logConsoleProgress(`Downloading ${name} to My Notebooks`); const startKey = traceStart(Action.NotebooksGalleryDownload, { notebookId: data.id, downloadCount: data.downloads, @@ -249,10 +244,7 @@ export function downloadItem( removeNotebookViewerLink(notebook, data.newCellId); await container.importAndOpenContent(data.name, JSON.stringify(notebook)); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `Successfully downloaded ${name} to My Notebooks` - ); + logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`); const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id); if (increaseDownloadResponse.data) { @@ -279,7 +271,7 @@ export function downloadItem( handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`); } - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); + clearInProgressMessage(); }, "Cancel", undefined @@ -405,11 +397,7 @@ export function deleteItem( beforeDelete(); } const name = data.name; - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Removing ${name} from gallery` - ); - + const clearInProgressMessage = logConsoleProgress(`Removing ${name} from gallery`); const startKey = traceStart(Action.NotebooksGalleryDelete, { notebookId: data.id }); try { @@ -420,7 +408,7 @@ export function deleteItem( traceSuccess(Action.NotebooksGalleryDelete, { notebookId: data.id }, startKey); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully removed ${name} from gallery`); + logConsoleInfo(`Successfully removed ${name} from gallery`); onComplete(response.data); } catch (error) { traceFailure( @@ -436,7 +424,7 @@ export function deleteItem( } } - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); + clearInProgressMessage(); }, "Cancel", undefined diff --git a/src/Utils/NotificationConsoleUtils.ts b/src/Utils/NotificationConsoleUtils.ts index c003f7aa4..b661d7d78 100644 --- a/src/Utils/NotificationConsoleUtils.ts +++ b/src/Utils/NotificationConsoleUtils.ts @@ -3,79 +3,29 @@ import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/Notificat const _global = typeof self === "undefined" ? window : self; -/** - * @deprecated - * Use logConsoleInfo, logConsoleError, logConsoleProgress instead - * */ -export function logConsoleMessage(type: ConsoleDataType, message: string, id?: string): string { - const dataExplorer = _global.dataExplorer; - if (dataExplorer) { - const date = new Date(); - const formattedDate: string = new Intl.DateTimeFormat("en-EN", { - hour12: true, - hour: "numeric", - minute: "numeric", - }).format(date); - if (!id) { - id = _.uniqueId(); - } - dataExplorer.logConsoleData({ type, date: formattedDate, message, id }); - } - return id || ""; -} - -export function clearInProgressMessageWithId(id: string): void { - _global.dataExplorer?.deleteInProgressConsoleDataWithId(id); -} - -export function logConsoleProgress(message: string): () => void { - const type = ConsoleDataType.InProgress; +function log(type: ConsoleDataType, message: string): () => void { const dataExplorer = _global.dataExplorer; if (dataExplorer) { const id = _.uniqueId(); - const date = new Date(); - const formattedDate: string = new Intl.DateTimeFormat("en-EN", { + const date = new Intl.DateTimeFormat("en-EN", { hour12: true, hour: "numeric", minute: "numeric", - }).format(date); - dataExplorer.logConsoleData({ type, date: formattedDate, message, id }); - return () => { - dataExplorer.deleteInProgressConsoleDataWithId(id); - }; - } else { - return () => { - return; - }; + }).format(new Date()); + + dataExplorer.logConsoleData({ type, date, message, id }); + return () => dataExplorer.deleteInProgressConsoleDataWithId(id); } + + return () => undefined; } -export function logConsoleError(message: string): void { - const type = ConsoleDataType.Error; - const dataExplorer = _global.dataExplorer; - if (dataExplorer) { - const id = _.uniqueId(); - const date = new Date(); - const formattedDate: string = new Intl.DateTimeFormat("en-EN", { - hour12: true, - hour: "numeric", - minute: "numeric", - }).format(date); - dataExplorer.logConsoleData({ type, date: formattedDate, message, id }); - } -} +export const logConsoleProgress = (msg: string): (() => void) => log(ConsoleDataType.InProgress, msg); -export function logConsoleInfo(message: string): void { - const type = ConsoleDataType.Info; - const dataExplorer = _global.dataExplorer; - if (dataExplorer) { - const id = _.uniqueId(); - const date = new Date(); - const formattedDate: string = new Intl.DateTimeFormat("en-EN", { - hour12: true, - hour: "numeric", - minute: "numeric", - }).format(date); - dataExplorer.logConsoleData({ type, date: formattedDate, message, id }); - } -} +export const logConsoleError = (msg: string): void => { + log(ConsoleDataType.Error, msg); +}; + +export const logConsoleInfo = (msg: string): void => { + log(ConsoleDataType.Info, msg); +}; From ff58eb3724b633e072050d02af2daf5b96afe412 Mon Sep 17 00:00:00 2001 From: Hardikkumar Nai <80053762+hardiknai-techm@users.noreply.github.com> Date: Thu, 22 Apr 2021 00:39:19 +0530 Subject: [PATCH 16/23] Migrate Copy Notebook Pane to React (#640) Co-authored-by: Steve Faulkner --- src/Explorer/Controls/Dialog.tsx | 130 ++++++------ .../SettingsComponent.test.tsx.snap | 4 - src/Explorer/Explorer.tsx | 9 +- ...NotebookManager.ts => NotebookManager.tsx} | 63 +++--- src/Explorer/Panes/CopyNotebookPane.tsx | 197 ------------------ .../CopyNotebookPane/CopyNotebookPane.tsx | 156 ++++++++++++++ .../CopyNotebookPaneComponent.tsx | 89 ++++---- .../__snapshots__/SettingsPane.test.tsx.snap | 2 - .../UploadItemsPane.test.tsx.snap | 1 - ...eteDatabaseConfirmationPanel.test.tsx.snap | 1 - src/Main.tsx | 3 - 11 files changed, 305 insertions(+), 350 deletions(-) rename src/Explorer/Notebook/{NotebookManager.ts => NotebookManager.tsx} (92%) delete mode 100644 src/Explorer/Panes/CopyNotebookPane.tsx create mode 100644 src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx rename src/Explorer/Panes/{ => CopyNotebookPane}/CopyNotebookPaneComponent.tsx (52%) diff --git a/src/Explorer/Controls/Dialog.tsx b/src/Explorer/Controls/Dialog.tsx index 3b69e45be..ffb19b31d 100644 --- a/src/Explorer/Controls/Dialog.tsx +++ b/src/Explorer/Controls/Dialog.tsx @@ -1,8 +1,3 @@ -import * as React from "react"; -import { Dialog as FluentDialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog"; -import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; -import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; -import { Link } from "office-ui-fabric-react/lib/Link"; import { ChoiceGroup, FontIcon, @@ -10,6 +5,11 @@ import { IProgressIndicatorProps, ProgressIndicator, } from "office-ui-fabric-react"; +import { DefaultButton, IButtonProps, PrimaryButton } from "office-ui-fabric-react/lib/Button"; +import { Dialog as FluentDialog, DialogFooter, DialogType, IDialogProps } from "office-ui-fabric-react/lib/Dialog"; +import { Link } from "office-ui-fabric-react/lib/Link"; +import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; +import React, { FunctionComponent } from "react"; export interface TextFieldProps extends ITextFieldProps { label: string; @@ -50,61 +50,69 @@ const DIALOG_TITLE_FONT_SIZE = "17px"; const DIALOG_TITLE_FONT_WEIGHT = 400; const DIALOG_SUBTEXT_FONT_SIZE = "15px"; -export class Dialog extends React.Component { - constructor(props: DialogProps) { - super(props); - } - - public render(): JSX.Element { - const dialogProps: IDialogProps = { - hidden: !this.props.visible, - dialogContentProps: { - type: this.props.type || DialogType.normal, - title: this.props.title, - subText: this.props.subText, - styles: { - title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT }, - subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }, - }, - showCloseButton: this.props.showCloseButton || false, - onDismiss: this.props.onDismiss, +export const Dialog: FunctionComponent = ({ + title, + subText, + isModal, + visible, + choiceGroupProps, + textFieldProps, + linkProps, + progressIndicatorProps, + primaryButtonText, + secondaryButtonText, + onPrimaryButtonClick, + onSecondaryButtonClick, + primaryButtonDisabled, + type, + showCloseButton, + onDismiss, +}: DialogProps) => { + const dialogProps: IDialogProps = { + hidden: !visible, + dialogContentProps: { + type: type || DialogType.normal, + title, + subText, + styles: { + title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT }, + subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }, }, - modalProps: { isBlocking: this.props.isModal, isDarkOverlay: false }, - minWidth: DIALOG_MIN_WIDTH, - maxWidth: DIALOG_MAX_WIDTH, - }; - const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps; - const textFieldProps: ITextFieldProps = this.props.textFieldProps; - const linkProps: LinkProps = this.props.linkProps; - const progressIndicatorProps: IProgressIndicatorProps = this.props.progressIndicatorProps; - const primaryButtonProps: IButtonProps = { - text: this.props.primaryButtonText, - disabled: this.props.primaryButtonDisabled || false, - onClick: this.props.onPrimaryButtonClick, - }; - const secondaryButtonProps: IButtonProps = - this.props.secondaryButtonText && this.props.onSecondaryButtonClick - ? { - text: this.props.secondaryButtonText, - onClick: this.props.onSecondaryButtonClick, - } - : undefined; + showCloseButton: showCloseButton || false, + onDismiss, + }, + modalProps: { isBlocking: isModal, isDarkOverlay: false }, + minWidth: DIALOG_MIN_WIDTH, + maxWidth: DIALOG_MAX_WIDTH, + }; - return ( - - {choiceGroupProps && } - {textFieldProps && } - {linkProps && ( - - {linkProps.linkText} - - )} - {progressIndicatorProps && } - - - {secondaryButtonProps && } - - - ); - } -} + const primaryButtonProps: IButtonProps = { + text: primaryButtonText, + disabled: primaryButtonDisabled || false, + onClick: onPrimaryButtonClick, + }; + const secondaryButtonProps: IButtonProps = + secondaryButtonText && onSecondaryButtonClick + ? { + text: secondaryButtonText, + onClick: onSecondaryButtonClick, + } + : undefined; + + return ( + + {choiceGroupProps && } + {textFieldProps && } + {linkProps && ( + + {linkProps.linkText} + + )} + {progressIndicatorProps && } + + + {secondaryButtonProps && } + + + ); +}; diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index c9c5fab96..893360470 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -552,7 +552,6 @@ exports[`SettingsComponent renders 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], @@ -1214,7 +1213,6 @@ exports[`SettingsComponent renders 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], @@ -1889,7 +1887,6 @@ exports[`SettingsComponent renders 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], @@ -2551,7 +2548,6 @@ exports[`SettingsComponent renders 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index e79d99c99..42e3f2fbf 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -186,12 +186,10 @@ export default class Explorer { public stringInputPane: StringInputPane; public gitHubReposPane: ContextualPaneBase; public publishNotebookPaneAdapter: ReactAdapter; - public copyNotebookPaneAdapter: ReactAdapter; // features public isGitHubPaneEnabled: ko.Observable; public isPublishNotebookPaneEnabled: ko.Observable; - public isCopyNotebookPaneEnabled: ko.Observable; public isHostedDataExplorerEnabled: ko.Computed; public isRightPanelV2Enabled: ko.Computed; public isMongoIndexingEnabled: ko.Observable; @@ -341,7 +339,6 @@ export default class Explorer { this.isGitHubPaneEnabled = ko.observable(false); this.isMongoIndexingEnabled = ko.observable(false); this.isPublishNotebookPaneEnabled = ko.observable(false); - this.isCopyNotebookPaneEnabled = ko.observable(false); this.canExceedMaximumValue = ko.computed(() => userContext.features.canExceedMaximumValue); @@ -1457,11 +1454,7 @@ export default class Explorer { } public copyNotebook(name: string, content: string): void { - if (this.notebookManager) { - this.notebookManager.openCopyNotebookPane(name, content); - this.copyNotebookPaneAdapter = this.notebookManager.copyNotebookPaneAdapter; - this.isCopyNotebookPaneEnabled(true); - } + this.notebookManager?.openCopyNotebookPane(name, content); } public showOkModalDialog(title: string, msg: string): void { diff --git a/src/Explorer/Notebook/NotebookManager.ts b/src/Explorer/Notebook/NotebookManager.tsx similarity index 92% rename from src/Explorer/Notebook/NotebookManager.ts rename to src/Explorer/Notebook/NotebookManager.tsx index b59c3377b..42f41baf5 100644 --- a/src/Explorer/Notebook/NotebookManager.ts +++ b/src/Explorer/Notebook/NotebookManager.tsx @@ -2,30 +2,31 @@ * Contains all notebook related stuff meant to be dynamically loaded by explorer */ -import { JunoClient } from "../../Juno/JunoClient"; -import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; -import { GitHubClient } from "../../GitHub/GitHubClient"; -import * as Logger from "../../Common/Logger"; -import { HttpStatusCodes, Areas } from "../../Common/Constants"; -import { GitHubReposPane } from "../Panes/GitHubReposPane"; -import ko from "knockout"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import { IContentProvider } from "@nteract/core"; -import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider"; -import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider"; -import { contents } from "rx-jupyter"; -import { NotebookContainerClient } from "./NotebookContainerClient"; -import { MemoryUsageInfo } from "../../Contracts/DataModels"; -import { NotebookContentClient } from "./NotebookContentClient"; -import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; -import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter"; -import { getFullName } from "../../Utils/UserUtils"; import { ImmutableNotebook } from "@nteract/commutable"; +import { IContentProvider } from "@nteract/core"; +import ko from "knockout"; +import React from "react"; +import { contents } from "rx-jupyter"; +import { Areas, HttpStatusCodes } from "../../Common/Constants"; +import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; +import * as Logger from "../../Common/Logger"; +import { MemoryUsageInfo } from "../../Contracts/DataModels"; +import { GitHubClient } from "../../GitHub/GitHubClient"; +import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider"; +import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; +import { JunoClient } from "../../Juno/JunoClient"; +import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; +import { getFullName } from "../../Utils/UserUtils"; import Explorer from "../Explorer"; import { ContextualPaneBase } from "../Panes/ContextualPaneBase"; -import { CopyNotebookPaneAdapter } from "../Panes/CopyNotebookPane"; -import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; +import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane"; +import { GitHubReposPane } from "../Panes/GitHubReposPane"; +import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter"; +import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; +import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider"; +import { NotebookContainerClient } from "./NotebookContainerClient"; +import { NotebookContentClient } from "./NotebookContentClient"; export interface NotebookManagerOptions { container: Explorer; @@ -49,7 +50,6 @@ export default class NotebookManager { public gitHubReposPane: ContextualPaneBase; public publishNotebookPaneAdapter: PublishNotebookPaneAdapter; - public copyNotebookPaneAdapter: CopyNotebookPaneAdapter; public initialize(params: NotebookManagerOptions): void { this.params = params; @@ -89,12 +89,6 @@ export default class NotebookManager { this.publishNotebookPaneAdapter = new PublishNotebookPaneAdapter(this.params.container, this.junoClient); - this.copyNotebookPaneAdapter = new CopyNotebookPaneAdapter( - this.params.container, - this.junoClient, - this.gitHubOAuthService - ); - this.gitHubOAuthService.getTokenObservable().subscribe((token) => { this.gitHubClient.setToken(token?.access_token); @@ -129,7 +123,18 @@ export default class NotebookManager { } public openCopyNotebookPane(name: string, content: string): void { - this.copyNotebookPaneAdapter.open(name, content); + const { container } = this.params; + container.openSidePanel( + "Copy Notebook", + + ); } // Octokit's error handler uses any diff --git a/src/Explorer/Panes/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane.tsx deleted file mode 100644 index a5b429aa7..000000000 --- a/src/Explorer/Panes/CopyNotebookPane.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import ko from "knockout"; -import { IDropdownOption } from "office-ui-fabric-react"; -import * as React from "react"; -import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; -import { HttpStatusCodes } from "../../Common/Constants"; -import { getErrorMessage, handleError } from "../../Common/ErrorHandlingUtils"; -import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; -import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient"; -import * as GitHubUtils from "../../Utils/GitHubUtils"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import Explorer from "../Explorer"; -import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; -import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; -import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; -import { - GenericRightPaneComponent, - GenericRightPaneProps, -} from "./GenericRightPaneComponent/GenericRightPaneComponent"; - -interface Location { - type: "MyNotebooks" | "GitHub"; - - // GitHub - owner?: string; - repo?: string; - branch?: string; -} - -export class CopyNotebookPaneAdapter implements ReactAdapter { - private static readonly BranchNameWhiteSpace = " "; - - parameters: ko.Observable; - private isOpened: boolean; - private isExecuting: boolean; - private formError: string; - private formErrorDetail: string; - private name: string; - private content: string; - private pinnedRepos: IPinnedRepo[]; - private selectedLocation: Location; - - constructor( - private container: Explorer, - private junoClient: JunoClient, - private gitHubOAuthService: GitHubOAuthService - ) { - this.parameters = ko.observable(Date.now()); - this.reset(); - this.triggerRender(); - } - - public renderComponent(): JSX.Element { - if (!this.isOpened) { - return undefined; - } - - const genericPaneProps: GenericRightPaneProps = { - container: this.container, - formError: this.formError, - formErrorDetail: this.formErrorDetail, - id: "copynotebookpane", - isExecuting: this.isExecuting, - title: "Copy notebook", - submitButtonText: "OK", - onClose: () => this.close(), - onSubmit: () => this.submit(), - }; - - const copyNotebookPaneProps: CopyNotebookPaneProps = { - name: this.name, - pinnedRepos: this.pinnedRepos, - onDropDownChange: this.onDropDownChange, - }; - - return ( - - - - ); - } - - public triggerRender(): void { - window.requestAnimationFrame(() => this.parameters(Date.now())); - } - - public async open(name: string, content: string): Promise { - this.name = name; - this.content = content; - - this.isOpened = true; - this.triggerRender(); - - if (this.gitHubOAuthService.isLoggedIn()) { - const response = await this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit"); - } - - if (response.data?.length > 0) { - this.pinnedRepos = response.data; - this.triggerRender(); - } - } - } - - public close(): void { - this.reset(); - this.triggerRender(); - } - - public async submit(): Promise { - let destination: string = this.selectedLocation?.type; - let clearMessage: () => void; - this.isExecuting = true; - this.triggerRender(); - - try { - if (!this.selectedLocation) { - throw new Error(`No location selected`); - } - - if (this.selectedLocation.type === "GitHub") { - destination = `${destination} - ${GitHubUtils.toRepoFullName( - this.selectedLocation.owner, - this.selectedLocation.repo - )} - ${this.selectedLocation.branch}`; - } - - clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${this.name} to ${destination}`); - - const notebookContentItem = await this.copyNotebook(this.selectedLocation); - if (!notebookContentItem) { - throw new Error(`Failed to upload ${this.name}`); - } - - NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${this.name} to ${destination}`); - } catch (error) { - const errorMessage = getErrorMessage(error); - this.formError = `Failed to copy ${this.name} to ${destination}`; - this.formErrorDetail = `${errorMessage}`; - handleError(errorMessage, "CopyNotebookPaneAdapter/submit", this.formError); - return; - } finally { - clearMessage && clearMessage(); - this.isExecuting = false; - this.triggerRender(); - } - - this.close(); - } - - private copyNotebook = async (location: Location): Promise => { - let parent: NotebookContentItem; - switch (location.type) { - case "MyNotebooks": - parent = { - name: ResourceTreeAdapter.MyNotebooksTitle, - path: this.container.getNotebookBasePath(), - type: NotebookContentItemType.Directory, - }; - break; - - case "GitHub": - parent = { - name: ResourceTreeAdapter.GitHubReposTitle, - path: GitHubUtils.toContentUri( - this.selectedLocation.owner, - this.selectedLocation.repo, - this.selectedLocation.branch, - "" - ), - type: NotebookContentItemType.Directory, - }; - break; - - default: - throw new Error(`Unsupported location type ${location.type}`); - } - - return this.container.uploadFile(this.name, this.content, parent); - }; - - private onDropDownChange = (_: React.FormEvent, option?: IDropdownOption): void => { - this.selectedLocation = option?.data; - }; - - private reset = (): void => { - this.isOpened = false; - this.isExecuting = false; - this.formError = undefined; - this.formErrorDetail = undefined; - this.name = undefined; - this.content = undefined; - this.pinnedRepos = undefined; - this.selectedLocation = undefined; - }; -} diff --git a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx new file mode 100644 index 000000000..3f4c281aa --- /dev/null +++ b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx @@ -0,0 +1,156 @@ +import { IDropdownOption } from "office-ui-fabric-react"; +import React, { FormEvent, FunctionComponent, useEffect, useState } from "react"; +import { HttpStatusCodes } from "../../../Common/Constants"; +import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; +import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; +import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient"; +import * as GitHubUtils from "../../../Utils/GitHubUtils"; +import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; +import Explorer from "../../Explorer"; +import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem"; +import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter"; +import { + GenericRightPaneComponent, + GenericRightPaneProps, +} from "../GenericRightPaneComponent/GenericRightPaneComponent"; +import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; + +interface Location { + type: "MyNotebooks" | "GitHub"; + + // GitHub + owner?: string; + repo?: string; + branch?: string; +} +export interface CopyNotebookPanelProps { + name: string; + content: string; + container: Explorer; + junoClient: JunoClient; + gitHubOAuthService: GitHubOAuthService; + closePanel: () => void; +} + +export const CopyNotebookPane: FunctionComponent = ({ + name, + content, + container, + junoClient, + gitHubOAuthService, + closePanel, +}: CopyNotebookPanelProps) => { + const [isExecuting, setIsExecuting] = useState(); + const [formError, setFormError] = useState(""); + const [formErrorDetail, setFormErrorDetail] = useState(""); + const [pinnedRepos, setPinnedRepos] = useState(); + const [selectedLocation, setSelectedLocation] = useState(); + + useEffect(() => { + open(); + }, []); + + const open = async (): Promise => { + if (gitHubOAuthService.isLoggedIn()) { + const response = await junoClient.getPinnedRepos(gitHubOAuthService.getTokenObservable()()?.scope); + if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { + handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit"); + } + + if (response.data?.length > 0) { + setPinnedRepos(response.data); + } + } + }; + + const submit = async (): Promise => { + let destination: string = selectedLocation?.type; + let clearMessage: () => void; + setIsExecuting(true); + + try { + if (!selectedLocation) { + throw new Error(`No location selected`); + } + + if (selectedLocation.type === "GitHub") { + destination = `${destination} - ${GitHubUtils.toRepoFullName( + selectedLocation.owner, + selectedLocation.repo + )} - ${selectedLocation.branch}`; + } + + clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`); + + const notebookContentItem = await copyNotebook(selectedLocation); + if (!notebookContentItem) { + throw new Error(`Failed to upload ${name}`); + } + + NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`); + closePanel(); + } catch (error) { + const errorMessage = getErrorMessage(error); + setFormError(`Failed to copy ${name} to ${destination}`); + setFormErrorDetail(`${errorMessage}`); + handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError); + } finally { + clearMessage && clearMessage(); + setIsExecuting(false); + } + }; + + const copyNotebook = async (location: Location): Promise => { + let parent: NotebookContentItem; + switch (location.type) { + case "MyNotebooks": + parent = { + name: ResourceTreeAdapter.MyNotebooksTitle, + path: container.getNotebookBasePath(), + type: NotebookContentItemType.Directory, + }; + break; + + case "GitHub": + parent = { + name: ResourceTreeAdapter.GitHubReposTitle, + path: GitHubUtils.toContentUri(selectedLocation.owner, selectedLocation.repo, selectedLocation.branch, ""), + type: NotebookContentItemType.Directory, + }; + break; + + default: + throw new Error(`Unsupported location type ${location.type}`); + } + + return container.uploadFile(name, content, parent); + }; + + const onDropDownChange = (_: FormEvent, option?: IDropdownOption): void => { + setSelectedLocation(option?.data); + }; + + const genericPaneProps: GenericRightPaneProps = { + container, + formError, + formErrorDetail, + id: "copynotebookpane", + isExecuting: isExecuting, + title: "Copy notebook", + submitButtonText: "OK", + onClose: closePanel, + onSubmit: () => submit(), + }; + + const copyNotebookPaneProps: CopyNotebookPaneProps = { + name, + pinnedRepos, + onDropDownChange: onDropDownChange, + }; + + return ( + + + + ); +}; diff --git a/src/Explorer/Panes/CopyNotebookPaneComponent.tsx b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx similarity index 52% rename from src/Explorer/Panes/CopyNotebookPaneComponent.tsx rename to src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx index 7ae30ccfe..915c0bd30 100644 --- a/src/Explorer/Panes/CopyNotebookPaneComponent.tsx +++ b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx @@ -1,18 +1,18 @@ -import * as GitHubUtils from "../../Utils/GitHubUtils"; -import * as React from "react"; -import { IPinnedRepo } from "../../Juno/JunoClient"; -import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; import { - Stack, - Label, - Text, Dropdown, - IDropdownProps, IDropdownOption, - SelectableOptionMenuItemType, + IDropdownProps, IRenderFunction, ISelectableOption, + Label, + SelectableOptionMenuItemType, + Stack, + Text, } from "office-ui-fabric-react"; +import React, { FormEvent, FunctionComponent } from "react"; +import { IPinnedRepo } from "../../../Juno/JunoClient"; +import * as GitHubUtils from "../../../Utils/GitHubUtils"; +import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter"; interface Location { type: "MyNotebooks" | "GitHub"; @@ -26,46 +26,25 @@ interface Location { export interface CopyNotebookPaneProps { name: string; pinnedRepos: IPinnedRepo[]; - onDropDownChange: (_: React.FormEvent, option?: IDropdownOption) => void; + onDropDownChange: (_: FormEvent, option?: IDropdownOption) => void; } -export class CopyNotebookPaneComponent extends React.Component { - private static readonly BranchNameWhiteSpace = " "; +export const CopyNotebookPaneComponent: FunctionComponent = ({ + name, + pinnedRepos, + onDropDownChange, +}: CopyNotebookPaneProps) => { + const BranchNameWhiteSpace = " "; - public render(): JSX.Element { - const dropDownProps: IDropdownProps = { - label: "Location", - ariaLabel: "Location", - placeholder: "Select an option", - onRenderTitle: this.onRenderDropDownTitle, - onRenderOption: this.onRenderDropDownOption, - options: this.getDropDownOptions(), - onChange: this.props.onDropDownChange, - }; - - return ( -
    - - - - {this.props.name} - - - - -
    - ); - } - - private onRenderDropDownTitle: IRenderFunction = (options: IDropdownOption[]): JSX.Element => { + const onRenderDropDownTitle: IRenderFunction = (options: IDropdownOption[]): JSX.Element => { return {options.length && options[0].title}; }; - private onRenderDropDownOption: IRenderFunction = (option: ISelectableOption): JSX.Element => { + const onRenderDropDownOption: IRenderFunction = (option: ISelectableOption): JSX.Element => { return {option.text}; }; - private getDropDownOptions = (): IDropdownOption[] => { + const getDropDownOptions = (): IDropdownOption[] => { const options: IDropdownOption[] = []; options.push({ @@ -77,7 +56,7 @@ export class CopyNotebookPaneComponent extends React.Component 0) { + if (pinnedRepos && pinnedRepos.length > 0) { options.push({ key: "GitHub-Header-Divider", text: undefined, @@ -90,7 +69,7 @@ export class CopyNotebookPaneComponent extends React.Component { + pinnedRepos.forEach((pinnedRepo) => { const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name); options.push({ key: `GitHub-Repo-${repoFullName}`, @@ -101,7 +80,7 @@ export class CopyNotebookPaneComponent extends React.Component options.push({ key: `GitHub-Repo-${repoFullName}-${branch.name}`, - text: `${CopyNotebookPaneComponent.BranchNameWhiteSpace}${branch.name}`, + text: `${BranchNameWhiteSpace}${branch.name}`, title: `${repoFullName} - ${branch.name}`, data: { type: "GitHub", @@ -116,4 +95,26 @@ export class CopyNotebookPaneComponent extends React.Component + + + + {name} + + + + +
    + ); +}; diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 4df3f4a52..456e08b1c 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -528,7 +528,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], @@ -1313,7 +1312,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap index 9619eadf7..03397b4a8 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap @@ -528,7 +528,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 572882ac5..ca0674169 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -529,7 +529,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "hasStorageAnalyticsAfecFeature": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], - "isCopyNotebookPaneEnabled": [Function], "isEnableMongoCapabilityPresent": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isGitHubPaneEnabled": [Function], diff --git a/src/Main.tsx b/src/Main.tsx index 48b223c82..5875afc57 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -243,9 +243,6 @@ const App: React.FunctionComponent = () => {
    - -
    - {showDialog && }
    ); From 9d411c57b0a9da3344f6d31c5b8340582f48dcb7 Mon Sep 17 00:00:00 2001 From: Hardikkumar Nai <80053762+hardiknai-techm@users.noreply.github.com> Date: Thu, 22 Apr 2021 03:11:08 +0530 Subject: [PATCH 17/23] Remove Explorer.isPreferredApiTable (#656) Co-authored-by: Steve Faulkner --- .../SubSettingsComponent.tsx | 2 +- .../SettingsComponent.test.tsx.snap | 4 -- .../ContainerSampleGenerator.test.ts | 1 - src/Explorer/Explorer.tsx | 13 +----- .../CommandBar/CommandBarComponentAdapter.tsx | 1 - .../CommandBarComponentButtonFactory.test.ts | 42 ++++++++++++++++--- .../CommandBarComponentButtonFactory.tsx | 2 +- src/Explorer/Panes/AddCollectionPane.ts | 10 ++--- .../__snapshots__/SettingsPane.test.tsx.snap | 2 - .../Panes/Tables/EditTableEntityPane.ts | 2 +- .../UploadItemsPane.test.tsx.snap | 1 - ...eteDatabaseConfirmationPanel.test.tsx.snap | 1 - src/Explorer/Tabs/QueryTablesTab.ts | 2 +- src/Explorer/Tree/Collection.ts | 4 +- src/RouteHandlers/TabRouteHandler.ts | 2 +- 15 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx index 5f05edd75..14947f81c 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx @@ -321,7 +321,7 @@ export class SubSettingsComponent extends React.Component { if ( userContext.apiType === "Cassandra" || - this.props.container.isPreferredApiTable() || + userContext.apiType === "Tables" || !this.props.collection.partitionKeyProperty || (this.props.container.isPreferredApiMongoDB() && this.props.collection.partitionKey.systemKey) ) { diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 893360470..c3778fdf3 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -561,7 +561,6 @@ exports[`SettingsComponent renders 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -1222,7 +1221,6 @@ exports[`SettingsComponent renders 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -1896,7 +1894,6 @@ exports[`SettingsComponent renders 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -2557,7 +2554,6 @@ exports[`SettingsComponent renders 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], diff --git a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts index 60308be0a..3b81ff56a 100644 --- a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts +++ b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts @@ -15,7 +15,6 @@ describe("ContainerSampleGenerator", () => { const explorerStub = {} as Explorer; explorerStub.databases = ko.observableArray([database]); explorerStub.isPreferredApiMongoDB = ko.computed(() => false); - explorerStub.isPreferredApiTable = ko.computed(() => false); explorerStub.canExceedMaximumValue = ko.computed(() => false); explorerStub.findDatabaseWithId = () => database; explorerStub.refreshAllDatabases = () => Q.resolve(); diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 42e3f2fbf..1341474eb 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -124,12 +124,6 @@ export default class Explorer { * Compare a string with userContext.apiType instead: userContext.apiType === "Mongo" * */ public isPreferredApiMongoDB: ko.Computed; - - /** - * @deprecated - * Compare a string with userContext.apiType instead: userContext.apiType === "Tables" - * */ - public isPreferredApiTable: ko.Computed; public isFixedCollectionWithSharedThroughputSupported: ko.Computed; /** * @deprecated @@ -402,11 +396,6 @@ export default class Explorer { }); }); - this.isPreferredApiTable = ko.computed(() => { - const defaultExperience = (this.defaultExperience && this.defaultExperience()) || ""; - return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Table.toLowerCase(); - }); - this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => { if (userContext.features.enableFixedCollectionWithSharedThroughput) { return true; @@ -499,7 +488,7 @@ export default class Explorer { }); this.addCollectionPane = new AddCollectionPane({ - isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()), + isPreferredApiTable: ko.computed(() => userContext.apiType === "Tables"), id: "addcollectionpane", visible: ko.observable(false), diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index d93407c22..917470dd4 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -29,7 +29,6 @@ export class CommandBarComponentAdapter implements ReactAdapter { // These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates const toWatch = [ - container.isPreferredApiTable, container.isPreferredApiMongoDB, container.deleteCollectionText, container.deleteDatabaseText, diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index 12deb7569..33fa59e0e 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -16,7 +16,13 @@ describe("CommandBarComponentButtonFactory tests", () => { beforeAll(() => { mockExplorer = {} as Explorer; mockExplorer.addCollectionText = ko.observable("mockText"); - mockExplorer.isPreferredApiTable = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableTable" }], + }, + } as DatabaseAccount, + }); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSynapseLinkUpdating = ko.observable(false); @@ -54,7 +60,13 @@ describe("CommandBarComponentButtonFactory tests", () => { beforeAll(() => { mockExplorer = {} as Explorer; mockExplorer.addCollectionText = ko.observable("mockText"); - mockExplorer.isPreferredApiTable = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableTable" }], + }, + } as DatabaseAccount, + }); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSparkEnabled = ko.observable(true); @@ -117,7 +129,13 @@ describe("CommandBarComponentButtonFactory tests", () => { beforeAll(() => { mockExplorer = {} as Explorer; mockExplorer.addCollectionText = ko.observable("mockText"); - mockExplorer.isPreferredApiTable = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableTable" }], + }, + } as DatabaseAccount, + }); mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSynapseLinkUpdating = ko.observable(false); @@ -195,8 +213,15 @@ describe("CommandBarComponentButtonFactory tests", () => { beforeAll(() => { mockExplorer = {} as Explorer; + mockExplorer.addDatabaseText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText"); - mockExplorer.isPreferredApiTable = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableTable" }], + }, + } as DatabaseAccount, + }); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSparkEnabled = ko.observable(true); @@ -226,6 +251,7 @@ describe("CommandBarComponentButtonFactory tests", () => { }, } as DatabaseAccount, }); + console.log(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeUndefined(); @@ -288,7 +314,13 @@ describe("CommandBarComponentButtonFactory tests", () => { beforeAll(() => { mockExplorer = {} as Explorer; mockExplorer.addCollectionText = ko.observable("mockText"); - mockExplorer.isPreferredApiTable = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableTable" }], + }, + } as DatabaseAccount, + }); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); mockExplorer.isSynapseLinkUpdating = ko.observable(false); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index b59e0f526..3f3533f66 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -47,7 +47,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto buttons.push(addSynapseLink); } - if (!container.isPreferredApiTable()) { + if (userContext.apiType !== "Tables") { newCollectionBtn.children = [createNewCollectionGroup(container)]; const newDatabaseBtn = createNewDatabase(container); newCollectionBtn.children.push(newDatabaseBtn); diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index e20786d55..ba2f93884 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -331,7 +331,7 @@ export default class AddCollectionPane extends ContextualPaneBase { if (currentCollections >= maxCollections) { let typeOfContainer = "collection"; - if (userContext.apiType === "Gremlin" || this.container.isPreferredApiTable()) { + if (userContext.apiType === "Gremlin" || userContext.apiType === "Tables") { typeOfContainer = "container"; } @@ -392,7 +392,7 @@ export default class AddCollectionPane extends ContextualPaneBase { }); this.partitionKeyVisible = ko.computed(() => { - if (this.container == null || !!this.container.isPreferredApiTable()) { + if (this.container == null || userContext.apiType === "Tables") { return false; } @@ -757,7 +757,7 @@ export default class AddCollectionPane extends ContextualPaneBase { return; } - if (!!this.container.isPreferredApiTable()) { + if (userContext.apiType === "Tables") { // Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk' this.databaseId(SharedConstants.CollectionCreation.TablesAPIDefaultDatabase); this.partitionKey("/'$pk'"); @@ -954,7 +954,7 @@ export default class AddCollectionPane extends ContextualPaneBase { } public isNonTableApi = (): boolean => { - return !this.container.isPreferredApiTable(); + return userContext.apiType !== "Tables"; }; public isUnlimitedStorageSelected = (): boolean => { @@ -1028,7 +1028,7 @@ export default class AddCollectionPane extends ContextualPaneBase { private _setFocus() { // Autofocus is enabled on AddCollectionPane based on the preferred API - if (this.container.isPreferredApiTable()) { + if (userContext.apiType === "Tables") { const focusTableId = document.getElementById("containerId"); focusTableId && focusTableId.focus(); return; diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 456e08b1c..953d44ccf 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -537,7 +537,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -1321,7 +1320,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], diff --git a/src/Explorer/Panes/Tables/EditTableEntityPane.ts b/src/Explorer/Panes/Tables/EditTableEntityPane.ts index a4f178feb..5b69fcd35 100644 --- a/src/Explorer/Panes/Tables/EditTableEntityPane.ts +++ b/src/Explorer/Panes/Tables/EditTableEntityPane.ts @@ -69,7 +69,7 @@ export default class EditTableEntityPane extends TableEntityPane { public open() { this.displayedAttributes(this.constructDisplayedAttributes(this.originEntity)); - if (this.container.isPreferredApiTable()) { + if (userContext.apiType === "Tables") { this.originalDocument = TableEntityProcessor.convertEntitiesToDocuments( [this.originEntity], this.tableViewModel.queryTablesTab.collection diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap index 03397b4a8..22e91a515 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap @@ -537,7 +537,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index ca0674169..505c81973 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -540,7 +540,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isPreferredApiMongoDB": [Function], - "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], diff --git a/src/Explorer/Tabs/QueryTablesTab.ts b/src/Explorer/Tabs/QueryTablesTab.ts index 2f8bc77ad..9da085a81 100644 --- a/src/Explorer/Tabs/QueryTablesTab.ts +++ b/src/Explorer/Tabs/QueryTablesTab.ts @@ -47,7 +47,7 @@ export default class QueryTablesTab extends TabsBase { this.tableEntityListViewModel().queryTablesTab = this; this.queryViewModel(new QueryViewModel(this)); const sampleQuerySubscription = this.tableEntityListViewModel().items.subscribe(() => { - if (this.tableEntityListViewModel().items().length > 0 && this.container.isPreferredApiTable()) { + if (this.tableEntityListViewModel().items().length > 0 && userContext.apiType === "Tables") { this.queryViewModel().queryBuilderViewModel().setExample(); } sampleQuerySubscription.dispose(); diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 09c58bbc0..ed525adfe 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -1069,7 +1069,7 @@ export default class Collection implements ViewModels.Collection { * Top-level method that will open the correct tab type depending on account API */ public openTab(): void { - if (this.container.isPreferredApiTable()) { + if (userContext.apiType === "Tables") { this.onTableEntitiesClick(); return; } else if (userContext.apiType === "Cassandra") { @@ -1090,7 +1090,7 @@ export default class Collection implements ViewModels.Collection { * Get correct collection label depending on account API */ public getLabel(): string { - if (this.container.isPreferredApiTable()) { + if (userContext.apiType === "Tables") { return "Entities"; } else if (userContext.apiType === "Cassandra") { return "Rows"; diff --git a/src/RouteHandlers/TabRouteHandler.ts b/src/RouteHandlers/TabRouteHandler.ts index 19a453c38..f20606cae 100644 --- a/src/RouteHandlers/TabRouteHandler.ts +++ b/src/RouteHandlers/TabRouteHandler.ts @@ -147,7 +147,7 @@ export class TabRouteHandler { ); collection && collection.container && - (collection.container.isPreferredApiTable() || userContext.apiType === "Cassandra") && + (userContext.apiType === "Tables" || userContext.apiType === "Cassandra") && collection.onTableEntitiesClick(); }); } From c6766dd69e61f50bf1b177e1433337d0e210fd11 Mon Sep 17 00:00:00 2001 From: vaidankarswapnil <81285216+vaidankarswapnil@users.noreply.github.com> Date: Thu, 22 Apr 2021 20:17:59 +0530 Subject: [PATCH 18/23] Migrate new graph vertex panel to react (#702) Co-authored-by: Steve Faulkner --- .eslintignore | 5 +- src/Explorer/ComponentRegisterer.test.ts | 8 - src/Explorer/ComponentRegisterer.ts | 3 - .../SettingsComponent.test.tsx.snap | 128 ----------- src/Explorer/Explorer.tsx | 10 - .../NewVertexComponent/NewVertex.test.ts | 75 ------ .../NewVertexComponent.html | 74 ------ .../NewVertexComponent.less | 97 ++++++++ .../NewVertexComponent.test.tsx | 118 ++++++++++ .../NewVertexComponent/NewVertexComponent.ts | 99 -------- .../NewVertexComponent/NewVertexComponent.tsx | 213 ++++++++++++++++++ .../NewVertexComponent.test.tsx.snap | 3 + .../newVertexComponent.less | 97 -------- src/Explorer/Panes/GraphNewVertexPane.html | 60 ----- src/Explorer/Panes/GraphNewVertexPane.less | 10 - src/Explorer/Panes/NewVertexPane.ts | 65 ------ .../Panes/NewVertexPanel/NewVertexPanel.less | 10 + .../NewVertexPanel/NewVertexPanel.test.tsx | 78 +++++++ .../Panes/NewVertexPanel/NewVertexPanel.tsx | 74 ++++++ .../NewVertexPanel.test.tsx.snap | 20 ++ src/Explorer/Panes/PaneComponents.ts | 10 - .../__snapshots__/SettingsPane.test.tsx.snap | 64 ------ .../UploadItemsPane.test.tsx.snap | 32 --- ...eteDatabaseConfirmationPanel.test.tsx.snap | 32 --- .../Tabs/{GraphTab.ts => GraphTab.tsx} | 89 ++++---- src/Main.tsx | 3 - 26 files changed, 663 insertions(+), 814 deletions(-) delete mode 100644 src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts delete mode 100644 src/Explorer/Graph/NewVertexComponent/NewVertexComponent.html create mode 100644 src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less create mode 100644 src/Explorer/Graph/NewVertexComponent/NewVertexComponent.test.tsx delete mode 100644 src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts create mode 100644 src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx create mode 100644 src/Explorer/Graph/NewVertexComponent/__snapshots__/NewVertexComponent.test.tsx.snap delete mode 100644 src/Explorer/Graph/NewVertexComponent/newVertexComponent.less delete mode 100644 src/Explorer/Panes/GraphNewVertexPane.html delete mode 100644 src/Explorer/Panes/GraphNewVertexPane.less delete mode 100644 src/Explorer/Panes/NewVertexPane.ts create mode 100644 src/Explorer/Panes/NewVertexPanel/NewVertexPanel.less create mode 100644 src/Explorer/Panes/NewVertexPanel/NewVertexPanel.test.tsx create mode 100644 src/Explorer/Panes/NewVertexPanel/NewVertexPanel.tsx create mode 100644 src/Explorer/Panes/NewVertexPanel/__snapshots__/NewVertexPanel.test.tsx.snap rename src/Explorer/Tabs/{GraphTab.ts => GraphTab.tsx} (82%) diff --git a/.eslintignore b/.eslintignore index 4ec15e766..94f7c3e83 100644 --- a/.eslintignore +++ b/.eslintignore @@ -90,8 +90,7 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts -src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts -src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts + src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts src/Explorer/Menus/ContextMenu.ts @@ -123,7 +122,7 @@ src/Explorer/Panes/ContextualPaneBase.ts src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts src/Explorer/Panes/GraphStylingPane.ts -src/Explorer/Panes/NewVertexPane.ts +# src/Explorer/Panes/NewVertexPane.ts src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/RenewAdHocAccessPane.ts src/Explorer/Panes/StringInputPane.ts diff --git a/src/Explorer/ComponentRegisterer.test.ts b/src/Explorer/ComponentRegisterer.test.ts index 626be3735..98f5af729 100644 --- a/src/Explorer/ComponentRegisterer.test.ts +++ b/src/Explorer/ComponentRegisterer.test.ts @@ -8,10 +8,6 @@ describe("Component Registerer", () => { expect(ko.components.isRegistered("input-typeahead")).toBe(true); }); - it("should register new-vertex-form component", () => { - expect(ko.components.isRegistered("new-vertex-form")).toBe(true); - }); - it("should register error-display component", () => { expect(ko.components.isRegistered("error-display")).toBe(true); }); @@ -73,10 +69,6 @@ describe("Component Registerer", () => { expect(ko.components.isRegistered("add-collection-pane")).toBe(true); }); - it("should register graph-new-vertex-pane component", () => { - expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true); - }); - it("should register graph-styling-pane component", () => { expect(ko.components.isRegistered("graph-styling-pane")).toBe(true); }); diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index a39a2ad33..e3eecc0a6 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -7,7 +7,6 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent"; -import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import * as PaneComponents from "./Panes/PaneComponents"; import ConflictsTab from "./Tabs/ConflictsTab"; import DocumentsTab from "./Tabs/DocumentsTab"; @@ -25,7 +24,6 @@ import TriggerTab from "./Tabs/TriggerTab"; import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab"; ko.components.register("input-typeahead", new InputTypeaheadComponent()); -ko.components.register("new-vertex-form", NewVertexComponent); ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("graph-style", GraphStyleComponent); ko.components.register("editor", new EditorComponent()); @@ -56,7 +54,6 @@ ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponent // Panes ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); -ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent()); ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index c3778fdf3..4fde12349 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -211,22 +211,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -571,22 +555,6 @@ exports[`SettingsComponent renders 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -871,22 +839,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -1231,22 +1183,6 @@ exports[`SettingsComponent renders 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -1544,22 +1480,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -1904,22 +1824,6 @@ exports[`SettingsComponent renders 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -2204,22 +2108,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -2564,22 +2452,6 @@ exports[`SettingsComponent renders 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 1341474eb..5e289f1f7 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -59,7 +59,6 @@ import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmat import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; import GraphStylingPane from "./Panes/GraphStylingPane"; import { LoadQueryPane } from "./Panes/LoadQueryPane/LoadQueryPane"; -import NewVertexPane from "./Panes/NewVertexPane"; import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane"; import { SettingsPane } from "./Panes/SettingsPane/SettingsPane"; import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; @@ -175,7 +174,6 @@ export default class Explorer { public addCollectionPane: AddCollectionPane; public graphStylingPane: GraphStylingPane; public editTableEntityPane: EditTableEntityPane; - public newVertexPane: NewVertexPane; public cassandraAddCollectionPane: CassandraAddCollectionPane; public stringInputPane: StringInputPane; public gitHubReposPane: ContextualPaneBase; @@ -509,13 +507,6 @@ export default class Explorer { container: this, }); - this.newVertexPane = new NewVertexPane({ - id: "newvertexpane", - visible: ko.observable(false), - - container: this, - }); - this.cassandraAddCollectionPane = new CassandraAddCollectionPane({ id: "cassandraaddcollectionpane", visible: ko.observable(false), @@ -543,7 +534,6 @@ export default class Explorer { this.addCollectionPane, this.graphStylingPane, this.editTableEntityPane, - this.newVertexPane, this.cassandraAddCollectionPane, this.stringInputPane, ]; diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts b/src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts deleted file mode 100644 index 6327f9d28..000000000 --- a/src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as ko from "knockout"; -import { NewVertexComponent, NewVertexViewModel } from "./NewVertexComponent"; - -const component = NewVertexComponent; - -describe("New Vertex Component", () => { - let vm: NewVertexViewModel; - let partitionKeyProperty: ko.Observable; - - beforeEach(async () => { - document.body.innerHTML = component.template as any; - partitionKeyProperty = ko.observable(null); - vm = new component.viewModel({ - newVertexData: null, - partitionKeyProperty, - }); - ko.applyBindings(vm); - }); - - afterEach(() => { - ko.cleanNode(document); - }); - - describe("Rendering", () => { - it("should display property list with input and +Add Property", () => { - expect(document.querySelector(".newVertexComponent .newVertexForm")).not.toBeNull(); - expect(document.querySelector(".newVertexComponent .edgeInput")).not.toBeNull(); - expect(document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn")).not.toBeNull(); - }); - - it("should display partition key property if set", () => { - partitionKeyProperty("testKey"); - expect( - (document.querySelector(".newVertexComponent .newVertexForm .labelCol input") as HTMLInputElement).value - ).toEqual("testKey"); - }); - - it("should NOT display partition key property if NOT set", () => { - expect(document.getElementsByClassName("valueCol").length).toBe(0); - }); - }); - - describe("Behavior", () => { - let clickSpy: jasmine.Spy; - - beforeEach(() => { - clickSpy = jasmine.createSpy("Command button click spy"); - }); - - it("should add new property row when +Add property button is pressed", () => { - document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click")); - document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click")); - document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click")); - expect(document.getElementsByClassName("valueCol").length).toBe(3); - expect(document.getElementsByClassName("rightPaneTrashIcon").length).toBe(3); - }); - - it("should remove property row when trash button is pressed", () => { - document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click")); - document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click")); - - // Mark this one to delete - const elts = document.querySelectorAll(".newVertexComponent .rightPaneTrashIconImg"); - elts[elts.length - 1].className += " deleteme"; - - document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click")); - document - .querySelector(".newVertexComponent .rightPaneTrashIconImg.deleteme") - .parentElement.dispatchEvent(new Event("click")); - expect(document.getElementsByClassName("valueCol").length).toBe(2); - expect(document.getElementsByClassName("rightPaneTrashIcon").length).toBe(2); - expect(document.querySelectorAll(".newVertexComponent .rightPaneTrashIconImg.deleteme").length).toBe(0); - }); - }); -}); diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.html b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.html deleted file mode 100644 index 1c17d19c0..000000000 --- a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.html +++ /dev/null @@ -1,74 +0,0 @@ -
    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    - Remove property -
    -
    -
    - - -
    - - - Add property Add - Property - -
    -
    -
    diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less new file mode 100644 index 000000000..7f214d22d --- /dev/null +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less @@ -0,0 +1,97 @@ +@import "../../../../less/Common/Constants"; + +.newVertexComponent { + padding: @LargeSpace 20px 20px 0px; + width: 400px; + + .newVertexForm { + width: 100%; + .flex-display(); + .flex-direction(); + + .newVertexFormRow { + .flex-display(); + .flex-direction(@direction: row); + padding: 4px 5px; + + label { + padding: 0px; + } + + .valueCol { + flex-grow: 1; + padding-right: 5px; + } + + .rightPaneAddPropertyBtnPadding { + padding-top: 14px; + } + + .edgeLabel { + padding-right: 41px; + } + } + } + + .actionCol { + min-width: 30px; + padding: 0px 4px; + } + + .labelCol { + width: 72px; + min-width: 72px; + + input { + max-width: 65px; + padding-left: 4px; + } + } + + .edgeInput { + width: 100%; + padding-left: 4px; + } + + .typeSelect { + height: 23px; + width: 70px; + } + + .rightPaneTrashIcon { + padding: 4px 1px 0px 4px; + height: 100%; + } + + .rightPaneTrashIconImg { + vertical-align: top; + } + + .rightPaneAddPropertyBtn { + padding: 7px 7px 8px 8px; + margin-left: -8px; + } + + .rightPaneBtns { + cursor: pointer; + + &:hover { + background-color: @BaseLow; + } + + &:active { + background-color: @AccentMediumLow; + } + } + + .rightPaneAddPropertyImg { + margin-right: 5px; + margin-bottom: 4px; + } + + .contentScroll { + overflow-y: auto; + overflow-x: hidden; + white-space: nowrap; + } +} diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.test.tsx b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.test.tsx new file mode 100644 index 000000000..d6437bc85 --- /dev/null +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.test.tsx @@ -0,0 +1,118 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import * as ViewModels from "../../../Contracts/ViewModels"; +import { NewVertexComponent } from "./NewVertexComponent"; + +describe("New Vertex Component", () => { + beforeEach(() => { + const fakeNewVertexData: ViewModels.NewVertexData = { + label: "", + properties: [ + { + key: "test1", + values: [ + { + value: "", + type: "string", + }, + ], + }, + ], + }; + const props = { + newVertexDataProp: fakeNewVertexData, + partitionKeyPropertyProp: "test1", + onChangeProp: (): void => undefined, + }; + + render(); + }); + + it("should render default prpoerty", () => { + const fakeNewVertexData: ViewModels.NewVertexData = { + label: "", + properties: [], + }; + const props = { + newVertexDataProp: fakeNewVertexData, + partitionKeyPropertyProp: "", + onChangeProp: (): void => undefined, + }; + + const { asFragment } = render(); + expect(asFragment).toMatchSnapshot(); + }); + + it("should render Add property button", () => { + const span = screen.getByText("Add Property"); + expect(span).toBeDefined(); + }); + + it("should call onAddNewProperty method on span click", () => { + const onAddNewProperty = jest.fn(); + const span = screen.getByText("Add Property"); + span.onclick = onAddNewProperty(); + fireEvent.click(span); + expect(onAddNewProperty).toHaveBeenCalled(); + }); + + it("should call onAddNewPropertyKeyPress method on span keyPress", () => { + const onAddNewPropertyKeyPress = jest.fn(); + const span = screen.getByText("Add Property"); + span.onkeypress = onAddNewPropertyKeyPress(); + fireEvent.keyPress(span, { key: "Enter", code: 13, charCode: 13 }); + expect(onAddNewPropertyKeyPress).toHaveBeenCalled(); + }); + + it("should call onLabelChange method on input change", () => { + const onLabelChange = jest.fn(); + const input = screen.getByLabelText("Label"); + input.onchange = onLabelChange(); + fireEvent.change(input, { target: { value: "Label" } }); + expect(onLabelChange).toHaveBeenCalled(); + }); + + it("should call onKeyChange method on key input change", () => { + const onKeyChange = jest.fn(); + const input = screen.queryByPlaceholderText("Key"); + input.onchange = onKeyChange(); + fireEvent.change(input, { target: { value: "pk1" } }); + expect(onKeyChange).toHaveBeenCalled(); + }); + + it("should call onValueChange method on value input change", () => { + const onValueChange = jest.fn(); + const input = screen.queryByPlaceholderText("Value"); + input.onchange = onValueChange(); + fireEvent.change(input, { target: { value: "abc" } }); + expect(onValueChange).toHaveBeenCalled(); + }); + + it("should call removeNewVertexProperty method on remove button click", () => { + const removeNewVertexProperty = jest.fn(); + const div = screen.getAllByRole("button"); + div[0].onclick = removeNewVertexProperty(); + fireEvent.click(div[0]); + expect(removeNewVertexProperty).toHaveBeenCalled(); + }); + + it("should call removeNewVertexProperty method on remove button keyPress", () => { + const removeNewVertexPropertyKeyPress = jest.fn(); + const div = screen.getAllByRole("button"); + div[0].onkeypress = removeNewVertexPropertyKeyPress(); + fireEvent.keyPress(div[0], { key: "Enter", code: 13, charCode: 13 }); + expect(removeNewVertexPropertyKeyPress).toHaveBeenCalled(); + }); + + it("should call onTypeChange method on type dropdown change", () => { + const DOWN_ARROW = { keyCode: 40 }; + const onTypeChange = jest.fn(); + const dropdown = screen.getByRole("listbox"); + dropdown.onclick = onTypeChange(); + dropdown.onkeydown = onTypeChange(); + + fireEvent.keyDown(screen.getByRole("listbox"), DOWN_ARROW); + fireEvent.click(screen.getByText(/number/)); + expect(onTypeChange).toHaveBeenCalled(); + }); +}); diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts deleted file mode 100644 index 50a3cead5..000000000 --- a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts +++ /dev/null @@ -1,99 +0,0 @@ -import * as ko from "knockout"; -import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent"; -import { NewVertexData, InputProperty } from "../../../Contracts/ViewModels"; -import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel"; -import * as Constants from "../../../Common/Constants"; -import template from "./NewVertexComponent.html"; - -/** - * Parameters for this component - */ -export interface NewVertexParams { - // Data to be edited by the component - newVertexData: ko.Observable; - partitionKeyProperty: ko.Observable; - firstFieldHasFocus?: ko.Observable; - - /** - * Callback triggered when the template is bound to the component (for testing purposes) - */ - onTemplateReady?: () => void; -} - -export class NewVertexViewModel extends WaitsForTemplateViewModel { - private static readonly DEFAULT_PROPERTY_TYPE = "string"; - - private newVertexData: ko.Observable; - private firstFieldHasFocus: ko.Observable; - private propertyTypes: string[]; - - public constructor(params: NewVertexParams) { - super(); - super.onTemplateReady((isTemplateReady: boolean) => { - if (isTemplateReady && params.onTemplateReady) { - params.onTemplateReady(); - } - }); - - this.newVertexData = - params.newVertexData || - ko.observable({ - label: "", - properties: [], - }); - this.firstFieldHasFocus = params.firstFieldHasFocus || ko.observable(false); - this.propertyTypes = EditorNodePropertiesComponent.VERTEX_PROPERTY_TYPES; - if (params.partitionKeyProperty) { - params.partitionKeyProperty.subscribe((newKeyProp: string) => { - if (!newKeyProp) { - return; - } - this.addNewVertexProperty(newKeyProp); - }); - } - } - - public onAddNewProperty() { - this.addNewVertexProperty(); - document.getElementById("propertyKeyNewVertexPane").focus(); - } - - public onAddNewPropertyKeyPress = (source: any, event: KeyboardEvent): boolean => { - if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) { - this.onAddNewProperty(); - event.stopPropagation(); - return false; - } - return true; - }; - - public addNewVertexProperty(key?: string) { - let ap = this.newVertexData().properties; - ap.push({ key: key || "", values: [{ value: "", type: NewVertexViewModel.DEFAULT_PROPERTY_TYPE }] }); - this.newVertexData.valueHasMutated(); - } - - public removeNewVertexProperty(index: number) { - let ap = this.newVertexData().properties; - ap.splice(index, 1); - this.newVertexData.valueHasMutated(); - document.getElementById("addProperyNewVertexBtn").focus(); - } - - public removeNewVertexPropertyKeyPress = (index: number, source: any, event: KeyboardEvent): boolean => { - if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) { - this.removeNewVertexProperty(index); - event.stopPropagation(); - return false; - } - return true; - }; -} - -/** - * Helper class for ko component registration - */ -export const NewVertexComponent = { - viewModel: NewVertexViewModel, - template, -}; diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx new file mode 100644 index 000000000..662dcba53 --- /dev/null +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx @@ -0,0 +1,213 @@ +import { Dropdown, IDropdownOption, Stack, TextField } from "office-ui-fabric-react"; +import React, { FunctionComponent, useRef, useState } from "react"; +import AddIcon from "../../../../images/Add-property.svg"; +import DeleteIcon from "../../../../images/delete.svg"; +import { NormalizedEventKey } from "../../../Common/Constants"; +import { GremlinPropertyValueType, InputPropertyValueTypeString, NewVertexData } from "../../../Contracts/ViewModels"; +import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent"; +import "./NewVertexComponent.less"; +export interface INewVertexComponentProps { + newVertexDataProp: NewVertexData; + partitionKeyPropertyProp: string; + onChangeProp: (labelData: NewVertexData) => void; +} + +export const NewVertexComponent: FunctionComponent = ({ + newVertexDataProp, + partitionKeyPropertyProp, + onChangeProp, +}: INewVertexComponentProps): JSX.Element => { + const DEFAULT_PROPERTY_TYPE = "string"; + const [newVertexData, setNewVertexData] = useState( + newVertexDataProp || { + label: "", + properties: [ + { + key: partitionKeyPropertyProp, + values: [{ value: "", type: DEFAULT_PROPERTY_TYPE }], + }, + ], + } + ); + + const propertyTypes: string[] = EditorNodePropertiesComponent.VERTEX_PROPERTY_TYPES; + const input = useRef(undefined); + + const onAddNewProperty = () => { + addNewVertexProperty(); + setTimeout(() => { + input.current.focus(); + }, 100); + }; + + const onAddNewPropertyKeyPress = (event: React.KeyboardEvent) => { + if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) { + onAddNewProperty(); + event.stopPropagation(); + } + }; + + const addNewVertexProperty = () => { + let key: string; + const ap = newVertexData.properties; + if (ap.length === 0) { + key = partitionKeyPropertyProp; + } + ap.push({ + key: key || "", + values: [{ value: "", type: DEFAULT_PROPERTY_TYPE }], + }); + setNewVertexData((prevData) => ({ + ...prevData, + properties: ap, + })); + onChangeProp(newVertexData); + }; + + const removeNewVertexProperty = (event?: React.MouseEvent, index?: number) => { + const ap = newVertexData.properties; + ap.splice(index, 1); + setNewVertexData((prevData) => ({ + ...prevData, + properties: ap, + })); + onChangeProp(newVertexData); + document.getElementById("addProperyNewVertexBtn").focus(); + }; + + const removeNewVertexPropertyKeyPress = (event: React.KeyboardEvent, index: number) => { + if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) { + removeNewVertexProperty(undefined, index); + event.stopPropagation(); + } + }; + + const onLabelChange = (event: React.ChangeEvent) => { + setNewVertexData((prevData) => ({ + ...prevData, + label: event.target.value, + })); + onChangeProp(newVertexData); + }; + + const onKeyChange = (event: React.ChangeEvent, index: number) => { + const newState = { ...newVertexData }; + newState.properties[index].key = event.target.value; + setNewVertexData(newState); + onChangeProp(newVertexData); + }; + + const onValueChange = (event: React.ChangeEvent, index: number) => { + const newState = { ...newVertexData }; + newState.properties[index].values[0].value = event.target.value as GremlinPropertyValueType; + setNewVertexData(newState); + onChangeProp(newVertexData); + }; + + const onTypeChange = (option: string, index: number) => { + const newState = { ...newVertexData }; + if (newState.properties[index]) { + newState.properties[index].values[0].type = option as InputPropertyValueTypeString; + setNewVertexData(newState); + onChangeProp(newVertexData); + } + }; + + return ( + +
    +
    +
    + ) => { + onLabelChange(event); + }} + /> +
    +
    + {newVertexData.properties.map((data, index) => { + return ( +
    +
    + ) => onKeyChange(event, index)} + /> +
    +
    + ) => onValueChange(event, index)} + /> +
    +
    + ({ + key: type, + text: type, + }))} + onChange={(_, options: IDropdownOption) => onTypeChange(options.key.toString(), index)} + /> +
    +
    +
    ) => removeNewVertexProperty(event, index)} + onKeyPress={(event: React.KeyboardEvent) => + removeNewVertexPropertyKeyPress(event, index) + } + > + Remove property +
    +
    +
    + ); + })} + +
    + + ) => onAddNewPropertyKeyPress(event)} + > + Add property Add Property + + +
    +
    +
    +
    + ); +}; diff --git a/src/Explorer/Graph/NewVertexComponent/__snapshots__/NewVertexComponent.test.tsx.snap b/src/Explorer/Graph/NewVertexComponent/__snapshots__/NewVertexComponent.test.tsx.snap new file mode 100644 index 000000000..629578b2b --- /dev/null +++ b/src/Explorer/Graph/NewVertexComponent/__snapshots__/NewVertexComponent.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`New Vertex Component should render default prpoerty 1`] = `[Function]`; diff --git a/src/Explorer/Graph/NewVertexComponent/newVertexComponent.less b/src/Explorer/Graph/NewVertexComponent/newVertexComponent.less deleted file mode 100644 index 00547ced6..000000000 --- a/src/Explorer/Graph/NewVertexComponent/newVertexComponent.less +++ /dev/null @@ -1,97 +0,0 @@ -@import "../../../../less/Common/Constants"; - -.newVertexComponent { - padding: @LargeSpace 20px 20px 0px; - width: 400px; - - .newVertexForm { - width: 100%; - .flex-display(); - .flex-direction(); - - .newVertexFormRow { - .flex-display(); - .flex-direction(@direction: row); - padding: 4px 5px; - - label { - padding: 0px; - } - - .valueCol { - flex-grow: 1; - padding-right: 5px; - } - - .rightPaneAddPropertyBtnPadding { - padding-top: 14px; - } - - .edgeLabel { - padding-right: 41px; - } - } - } - - .actionCol { - min-width: 30px; - padding: 0px 4px; - } - - .labelCol { - width: 72px; - min-width: 72px; - - input { - max-width: 65px; - padding-left: 4px; - } - } - - .edgeInput { - width: 100%; - padding-left: 4px; - } - - .typeSelect { - height: 23px; - width: 70px; - } - - .rightPaneTrashIcon { - padding: 4px 1px 0px 4px; - height: 100%; - } - - .rightPaneTrashIconImg { - vertical-align: top; - } - - .rightPaneAddPropertyBtn { - padding: 7px 7px 8px 8px; - margin-left: -8px; - } - - .rightPaneBtns { - cursor: pointer; - - &:hover { - background-color: @BaseLow ; - } - - &:active { - background-color: @AccentMediumLow; - } - } - - .rightPaneAddPropertyImg { - margin-right: 5px; - margin-bottom: 4px; - } - - .contentScroll { - overflow-y: auto; - overflow-x: hidden; - white-space: nowrap; - } -} diff --git a/src/Explorer/Panes/GraphNewVertexPane.html b/src/Explorer/Panes/GraphNewVertexPane.html deleted file mode 100644 index 980db6278..000000000 --- a/src/Explorer/Panes/GraphNewVertexPane.html +++ /dev/null @@ -1,60 +0,0 @@ -
    -
    -
    - -
    -
    - -
    - New Vertex -
    - Close -
    -
    - - - -
    -
    - Error - - - - More details - - -
    -
    - - - -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    diff --git a/src/Explorer/Panes/GraphNewVertexPane.less b/src/Explorer/Panes/GraphNewVertexPane.less deleted file mode 100644 index d084203e5..000000000 --- a/src/Explorer/Panes/GraphNewVertexPane.less +++ /dev/null @@ -1,10 +0,0 @@ -@import "../../../less/Common/Constants"; - -.newvertexContainer { - height:100%; - overflow-y: auto; - overflow-x: hidden; - white-space: nowrap; - .flex-display(); - .flex-direction(); -} \ No newline at end of file diff --git a/src/Explorer/Panes/NewVertexPane.ts b/src/Explorer/Panes/NewVertexPane.ts deleted file mode 100644 index d38e63965..000000000 --- a/src/Explorer/Panes/NewVertexPane.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as ko from "knockout"; -import * as ViewModels from "../../Contracts/ViewModels"; -import { ContextualPaneBase } from "./ContextualPaneBase"; -import { KeyCodes } from "../../Common/Constants"; -import Explorer from "../Explorer"; - -export default class NewVertexPane extends ContextualPaneBase { - public container: Explorer; - public visible: ko.Observable; - public formErrors: ko.Observable; - public formErrorsDetails: ko.Observable; - - // Graph style stuff - public tempVertexData: ko.Observable; // vertex data being edited - private onSubmitCreateCallback: (newVertexData: ViewModels.NewVertexData) => void; - private partitionKeyProperty: ko.Observable; - - constructor(options: ViewModels.PaneOptions) { - super(options); - this.tempVertexData = ko.observable(null); - this.partitionKeyProperty = ko.observable(null); - this.resetData(); - } - - public submit() { - // Commit edited changes - if (this.onSubmitCreateCallback != null) { - this.onSubmitCreateCallback(this.tempVertexData()); - } - - // this.close(); - } - - public resetData() { - super.resetData(); - - this.onSubmitCreateCallback = null; - - this.tempVertexData({ - label: "", - properties: [], - }); - this.partitionKeyProperty(null); - } - - public subscribeOnSubmitCreate(callback: (newVertexData: ViewModels.NewVertexData) => void): void { - this.onSubmitCreateCallback = callback; - } - - public setPartitionKeyProperty(pKeyProp: string): void { - this.partitionKeyProperty(pKeyProp); - } - - public onMoreDetailsKeyPress = (source: any, event: KeyboardEvent): boolean => { - if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) { - this.showErrorDetails(); - return false; - } - return true; - }; - - public buildString = (prefix: string, index: number): string => { - return `${prefix}${index}`; - }; -} diff --git a/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.less b/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.less new file mode 100644 index 000000000..fdf5730ac --- /dev/null +++ b/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.less @@ -0,0 +1,10 @@ +@import "../../../../less/Common/Constants"; + +.newvertexContainer { + height: 100%; + overflow-y: auto; + overflow-x: hidden; + white-space: nowrap; + .flex-display(); + .flex-direction(); +} diff --git a/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.test.tsx b/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.test.tsx new file mode 100644 index 000000000..f58265933 --- /dev/null +++ b/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.test.tsx @@ -0,0 +1,78 @@ +import { shallow, ShallowWrapper } from "enzyme"; +import React from "react"; +import * as ViewModels from "../../../Contracts/ViewModels"; +import Explorer from "../../Explorer"; +import { NewVertexPanel } from "./NewVertexPanel"; + +describe("New Vertex Panel", () => { + let fakeExplorer: Explorer; + let wrapper: ShallowWrapper; + + beforeEach(() => { + fakeExplorer = new Explorer(); + }); + + it("should render default property", () => { + const props = { + explorer: fakeExplorer, + partitionKeyPropertyProp: "", + onSubmit: (): void => undefined, + openNotificationConsole: (): void => undefined, + }; + wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + it("should render button in footer", () => { + const button = wrapper.find("PrimaryButton").first(); + expect(button).toBeDefined(); + }); + + it("should render form", () => { + const form = wrapper.find("form").first(); + expect(form).toBeDefined(); + }); + + it("should call form submit method", () => { + const onSubmitSpy = jest.fn(); + + const newWrapper = shallow( + undefined} + onSubmit={onSubmitSpy} + /> + ); + //eslint-disable-next-line + newWrapper.find("form").simulate("submit", { preventDefault: () => {} }); + + expect(onSubmitSpy).toHaveBeenCalled(); + }); + + it("should call error and success scenario method", () => { + const onSubmitSpy = jest.fn(); + const onErrorSpy = jest.fn(); + const onSuccessSpy = jest.fn(); + const fakeNewVertexData: ViewModels.NewVertexData = { + label: "", + properties: [], + }; + + const result = onSubmitSpy(fakeNewVertexData, onErrorSpy, onSuccessSpy); + + const newWrapper = shallow( + undefined} + onSubmit={onSubmitSpy} + /> + ); + //eslint-disable-next-line + newWrapper.find("form").simulate("submit", { preventDefault: () => {} }); + + expect(result).toBeUndefined(); + expect(onSubmitSpy).toHaveBeenCalledWith(fakeNewVertexData, onErrorSpy, onSuccessSpy); + }); +}); diff --git a/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.tsx b/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.tsx new file mode 100644 index 000000000..5440f6d1d --- /dev/null +++ b/src/Explorer/Panes/NewVertexPanel/NewVertexPanel.tsx @@ -0,0 +1,74 @@ +import { useBoolean } from "@uifabric/react-hooks"; +import React, { FunctionComponent, useState } from "react"; +import * as ViewModels from "../../../Contracts/ViewModels"; +import Explorer from "../../Explorer"; +import { NewVertexComponent } from "../../Graph/NewVertexComponent/NewVertexComponent"; +import { PanelFooterComponent } from "../PanelFooterComponent"; +import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; +import { PanelLoadingScreen } from "../PanelLoadingScreen"; +export interface INewVertexPanelProps { + explorer: Explorer; + partitionKeyPropertyProp: string; + onSubmit: (result: ViewModels.NewVertexData, onError: (errorMsg: string) => void, onSuccess: () => void) => void; + openNotificationConsole: () => void; +} + +export const NewVertexPanel: FunctionComponent = ({ + explorer, + partitionKeyPropertyProp, + onSubmit, + openNotificationConsole, +}: INewVertexPanelProps): JSX.Element => { + let newVertexDataValue: ViewModels.NewVertexData; + const [errorMessage, setErrorMessage] = useState(""); + const [showErrorDetails, setShowErrorDetails] = useState(false); + const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false); + const buttonLabel = "OK"; + + const submit = (event: React.MouseEvent) => { + event.preventDefault(); + setErrorMessage(undefined); + setShowErrorDetails(false); + if (onSubmit !== undefined) { + setLoadingTrue(); + onSubmit(newVertexDataValue, onError, onSuccess); + } + }; + + const onError = (errorMsg: string) => { + setErrorMessage(errorMsg); + setShowErrorDetails(true); + setLoadingFalse(); + }; + + const onSuccess = () => { + setLoadingFalse(); + explorer.closeSidePanel(); + }; + + const onChange = (newVertexData: ViewModels.NewVertexData) => { + newVertexDataValue = newVertexData; + }; + + return ( +
    ) => submit(event)}> + {errorMessage && ( + + )} +
    + +
    + + {isLoading && } + + ); +}; diff --git a/src/Explorer/Panes/NewVertexPanel/__snapshots__/NewVertexPanel.test.tsx.snap b/src/Explorer/Panes/NewVertexPanel/__snapshots__/NewVertexPanel.test.tsx.snap new file mode 100644 index 000000000..4d493e8c5 --- /dev/null +++ b/src/Explorer/Panes/NewVertexPanel/__snapshots__/NewVertexPanel.test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`New Vertex Panel should render default property 1`] = ` +
    +
    + +
    + + +`; diff --git a/src/Explorer/Panes/PaneComponents.ts b/src/Explorer/Panes/PaneComponents.ts index d9da4a530..d963a358b 100644 --- a/src/Explorer/Panes/PaneComponents.ts +++ b/src/Explorer/Panes/PaneComponents.ts @@ -2,7 +2,6 @@ import AddCollectionPaneTemplate from "./AddCollectionPane.html"; import AddDatabasePaneTemplate from "./AddDatabasePane.html"; import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html"; import GitHubReposPaneTemplate from "./GitHubReposPane.html"; -import GraphNewVertexPaneTemplate from "./GraphNewVertexPane.html"; import GraphStylingPaneTemplate from "./GraphStylingPane.html"; import StringInputPaneTemplate from "./StringInputPane.html"; import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html"; @@ -32,15 +31,6 @@ export class AddCollectionPaneComponent { } } -export class GraphNewVertexPaneComponent { - constructor() { - return { - viewModel: PaneComponent, - template: GraphNewVertexPaneTemplate, - }; - } -} - export class GraphStylingPaneComponent { constructor() { return { diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 953d44ccf..5602cffff 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -187,22 +187,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -547,22 +531,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -970,22 +938,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -1330,22 +1282,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap index 22e91a515..6cfb1497e 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/UploadItemsPane.test.tsx.snap @@ -187,22 +187,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -547,22 +531,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 505c81973..81eab5178 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -188,22 +188,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "title": [Function], "visible": [Function], }, - NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, CassandraAddCollectionPane { "autoPilotUsageCost": [Function], "canConfigureThroughput": [Function], @@ -551,22 +535,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "isSynapseLinkUpdating": [Function], "isTabsContentExpanded": [Function], "memoryUsageInfo": [Function], - "newVertexPane": NewVertexPane { - "buildString": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "id": "newvertexpane", - "isExecuting": [Function], - "isTemplateReady": [Function], - "onMoreDetailsKeyPress": [Function], - "onSubmitCreateCallback": null, - "partitionKeyProperty": [Function], - "tempVertexData": [Function], - "title": [Function], - "visible": [Function], - }, "notebookBasePath": [Function], "notebookServerInfo": [Function], "onRefreshDatabasesKeyPress": [Function], diff --git a/src/Explorer/Tabs/GraphTab.ts b/src/Explorer/Tabs/GraphTab.tsx similarity index 82% rename from src/Explorer/Tabs/GraphTab.ts rename to src/Explorer/Tabs/GraphTab.tsx index 5dffc6bfa..b30fa2469 100644 --- a/src/Explorer/Tabs/GraphTab.ts +++ b/src/Explorer/Tabs/GraphTab.tsx @@ -1,24 +1,24 @@ import * as ko from "knockout"; -import * as Q from "q"; -import * as ViewModels from "../../Contracts/ViewModels"; -import TabsBase from "./TabsBase"; -import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter"; -import { GraphAccessor, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer"; +import React from "react"; import NewVertexIcon from "../../../images/NewVertex.svg"; import StyleIcon from "../../../images/Style.svg"; -import GraphStylingPane from "../Panes/GraphStylingPane"; -import NewVertexPane from "../Panes/NewVertexPane"; import { DatabaseAccount } from "../../Contracts/DataModels"; +import * as ViewModels from "../../Contracts/ViewModels"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; +import { GraphAccessor, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer"; +import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter"; +import { ContextualPaneBase } from "../Panes/ContextualPaneBase"; +import GraphStylingPane from "../Panes/GraphStylingPane"; +import { NewVertexPanel } from "../Panes/NewVertexPanel/NewVertexPanel"; import template from "./GraphTab.html"; - +import TabsBase from "./TabsBase"; export interface GraphIconMap { [key: string]: { data: string; format: string }; } export interface GraphConfig { nodeColor: ko.Observable; - nodeColorKey: ko.Observable; // map property to node color. Takes precedence over nodeColor unless null + nodeColorKey: ko.Observable; // map property to node color. Takes precedence over nodeColor unless undefined linkColor: ko.Observable; showNeighborType: ko.Observable; nodeCaption: ko.Observable; @@ -53,21 +53,20 @@ export default class GraphTab extends TabsBase { private graphConfigUiData: ViewModels.GraphConfigUiData; private isFilterQueryLoading: ko.Observable; private isValidQuery: ko.Observable; - private newVertexPane: NewVertexPane; private graphStylingPane: GraphStylingPane; private collectionPartitionKeyProperty: string; + private contextualPane: ContextualPaneBase; constructor(options: GraphTabOptions) { super(options); - this.newVertexPane = options.collection && options.collection.container.newVertexPane; this.graphStylingPane = options.collection && options.collection.container.graphStylingPane; this.collectionPartitionKeyProperty = options.collectionPartitionKeyProperty; this.isNewVertexDisabled = ko.observable(false); this.isPropertyEditing = ko.observable(false); this.isGraphDisplayed = ko.observable(false); - this.graphAccessor = null; + this.graphAccessor = undefined; this.graphConfig = GraphTab.createGraphConfig(); // TODO Merge this with this.graphConfig this.graphConfigUiData = GraphTab.createGraphConfigUiData(this.graphConfig); @@ -99,8 +98,8 @@ export default class GraphTab extends TabsBase { masterKey: options.masterKey, onLoadStartKey: options.onLoadStartKey, onLoadStartKeyChange: (onLoadStartKey: number): void => { - if (onLoadStartKey == null) { - this.onLoadStartKey = null; + if (onLoadStartKey === undefined) { + this.onLoadStartKey = undefined; } }, resourceId: options.account.id, @@ -135,26 +134,31 @@ export default class GraphTab extends TabsBase { /* Command bar */ private showNewVertexEditor(): void { - this.newVertexPane.open(); - this.newVertexPane.setPartitionKeyProperty(this.collectionPartitionKeyProperty); - // TODO Must update GraphExplorer properties - this.newVertexPane.subscribeOnSubmitCreate((result: ViewModels.NewVertexData) => { - this.newVertexPane.formErrors(null); - this.newVertexPane.formErrorsDetails(null); - this.graphAccessor.addVertex(result).then( - () => { - this.newVertexPane.cancel(); - }, - (error: GraphExplorerError) => { - this.newVertexPane.formErrors(error.title); - if (!!error.details) { - this.newVertexPane.formErrorsDetails(error.details); - } - } - ); - }); + this.collection.container.openSidePanel( + "New Vertex", + this.collection.container.expandConsole()} + onSubmit={( + result: ViewModels.NewVertexData, + onError: (errorMessage: string) => void, + onSuccess: () => void + ): void => { + this.graphAccessor.addVertex(result).then( + () => { + onSuccess(); + this.contextualPane.cancel(); + }, + (error: GraphExplorerError) => { + onError(error.title); + } + ); + }} + /> + ); } - public openStyling() { + public openStyling(): void { this.setDefaultGraphConfigValues(); // Update the styling pane with this instance this.graphStylingPane.setData(this.graphConfigUiData); @@ -164,13 +168,13 @@ export default class GraphTab extends TabsBase { public static createGraphConfig(): GraphConfig { return { nodeColor: ko.observable(GraphTab.NODE_COLOR), - nodeColorKey: ko.observable(null), + nodeColorKey: ko.observable(undefined), linkColor: ko.observable(GraphTab.LINK_COLOR), showNeighborType: ko.observable(ViewModels.NeighborType.TARGETS_ONLY), nodeCaption: ko.observable(GraphTab.DEFAULT_NODE_CAPTION), nodeSize: ko.observable(GraphTab.NODE_SIZE), linkWidth: ko.observable(GraphTab.LINK_WIDTH), - nodeIconKey: ko.observable(null), + nodeIconKey: ko.observable(undefined), iconsMap: ko.observable({}), }; } @@ -183,26 +187,29 @@ export default class GraphTab extends TabsBase { nodeCaptionChoice: ko.observable(graphConfig.nodeCaption()), nodeColorKeyChoice: ko.observable(graphConfig.nodeColorKey()), nodeIconChoice: ko.observable(graphConfig.nodeIconKey()), - nodeIconSet: ko.observable(null), + nodeIconSet: ko.observable(undefined), }; } /** - * Make sure graph config values are not null + * Make sure graph config values are not undefined */ private setDefaultGraphConfigValues() { - // Assign default values if null - if (this.graphConfigUiData.nodeCaptionChoice() === null && this.graphConfigUiData.nodeProperties().length > 1) { + // Assign default values if undefined + if ( + this.graphConfigUiData.nodeCaptionChoice() === undefined && + this.graphConfigUiData.nodeProperties().length > 1 + ) { this.graphConfigUiData.nodeCaptionChoice(this.graphConfigUiData.nodeProperties()[0]); } if ( - this.graphConfigUiData.nodeColorKeyChoice() === null && + this.graphConfigUiData.nodeColorKeyChoice() === undefined && this.graphConfigUiData.nodePropertiesWithNone().length > 1 ) { this.graphConfigUiData.nodeColorKeyChoice(this.graphConfigUiData.nodePropertiesWithNone()[0]); } if ( - this.graphConfigUiData.nodeIconChoice() === null && + this.graphConfigUiData.nodeIconChoice() === undefined && this.graphConfigUiData.nodePropertiesWithNone().length > 1 ) { this.graphConfigUiData.nodeIconChoice(this.graphConfigUiData.nodePropertiesWithNone()[0]); diff --git a/src/Main.tsx b/src/Main.tsx index 5875afc57..901acda5e 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -39,12 +39,10 @@ import "./Explorer/Controls/ThroughputInput/ThroughputInput.less"; import "./Explorer/Controls/TreeComponent/treeComponent.less"; import { ExplorerParams } from "./Explorer/Explorer"; import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less"; -import "./Explorer/Graph/NewVertexComponent/newVertexComponent.less"; import "./Explorer/Menus/CommandBar/CommandBarComponent.less"; import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less"; import "./Explorer/Menus/NotificationConsole/NotificationConsole.less"; import { NotificationConsoleComponent } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; -import "./Explorer/Panes/GraphNewVertexPane.less"; import "./Explorer/Panes/PanelComponent.less"; import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponent"; import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen"; @@ -232,7 +230,6 @@ const App: React.FunctionComponent = () => { />
    -
    From 448566146fdcdecb0dde1f03f41e7b8ffa9dd89a Mon Sep 17 00:00:00 2001 From: Tanuj Mittal Date: Thu, 22 Apr 2021 13:37:12 -0400 Subject: [PATCH 19/23] Add CellOutputViewer for SandboxOutputs (#686) * Initial commit * Optimizations * Optimize notebookOutputViewer bundle size by lazy loading transforms * Update package-lock.json * More optimizations * Updates * Fix unit test and other updates * Address feedback * Update package-lock.json * Update test snapshots * Fix build * Reduce cellOutputViewer bundle size * Renaming --- externals/iframeResizer.contentWindow.min.js | 10 + package-lock.json | 592 ++++++++++++++++-- package.json | 7 +- src/CellOutputViewer/CellOutputViewer.tsx | 68 ++ src/CellOutputViewer/TransformMedia.tsx | 138 ++++ src/CellOutputViewer/cellOutputViewer.html | 12 + src/CellOutputViewer/transforms/Vega2.ts | 1 + src/CellOutputViewer/transforms/Vega3.ts | 1 + src/CellOutputViewer/transforms/Vega4.ts | 1 + src/CellOutputViewer/transforms/Vega5.ts | 1 + src/CellOutputViewer/transforms/VegaLite1.ts | 1 + src/CellOutputViewer/transforms/VegaLite2.ts | 1 + src/CellOutputViewer/transforms/VegaLite3.ts | 1 + src/CellOutputViewer/transforms/VegaLite4.ts | 1 + .../transforms/WidgetDisplay.ts | 1 + src/Explorer/Notebook/NotebookClientV2.ts | 114 ++-- .../NotebookReadOnlyRenderer.tsx | 17 +- .../NotebookRenderer/NotebookRenderer.tsx | 17 +- .../outputs/IFrameOutputs.tsx | 70 --- .../NotebookRenderer/outputs/SandboxFrame.tsx | 69 -- .../outputs/SandboxJavaScript.tsx | 26 - .../outputs/SandboxOutputs.tsx | 132 ++++ .../outputs/SanitizedHTML.tsx | 38 -- webpack.config.js | 13 + 24 files changed, 998 insertions(+), 334 deletions(-) create mode 100644 externals/iframeResizer.contentWindow.min.js create mode 100644 src/CellOutputViewer/CellOutputViewer.tsx create mode 100644 src/CellOutputViewer/TransformMedia.tsx create mode 100644 src/CellOutputViewer/cellOutputViewer.html create mode 100644 src/CellOutputViewer/transforms/Vega2.ts create mode 100644 src/CellOutputViewer/transforms/Vega3.ts create mode 100644 src/CellOutputViewer/transforms/Vega4.ts create mode 100644 src/CellOutputViewer/transforms/Vega5.ts create mode 100644 src/CellOutputViewer/transforms/VegaLite1.ts create mode 100644 src/CellOutputViewer/transforms/VegaLite2.ts create mode 100644 src/CellOutputViewer/transforms/VegaLite3.ts create mode 100644 src/CellOutputViewer/transforms/VegaLite4.ts create mode 100644 src/CellOutputViewer/transforms/WidgetDisplay.ts delete mode 100644 src/Explorer/Notebook/NotebookRenderer/outputs/IFrameOutputs.tsx delete mode 100644 src/Explorer/Notebook/NotebookRenderer/outputs/SandboxFrame.tsx delete mode 100644 src/Explorer/Notebook/NotebookRenderer/outputs/SandboxJavaScript.tsx create mode 100644 src/Explorer/Notebook/NotebookRenderer/outputs/SandboxOutputs.tsx delete mode 100644 src/Explorer/Notebook/NotebookRenderer/outputs/SanitizedHTML.tsx diff --git a/externals/iframeResizer.contentWindow.min.js b/externals/iframeResizer.contentWindow.min.js new file mode 100644 index 000000000..f711ae4fe --- /dev/null +++ b/externals/iframeResizer.contentWindow.min.js @@ -0,0 +1,10 @@ +/*! iFrame Resizer (iframeSizer.contentWindow.min.js) - v4.3.1 - 2021-01-11 + * Desc: Include this file in any page being loaded into an iframe + * to force the iframe to resize to the content size. + * Requires: iframeResizer.min.js on host page. + * Copyright: (c) 2021 David J. Bradshaw - dave@bradshaw.net + * License: MIT + */ + +!function(u){if("undefined"!=typeof window){var n=!0,o=10,i="",r=0,a="",t=null,c="",s=!1,d={resize:1,click:1},l=128,f=!0,m=1,h="bodyOffset",g=h,p=!0,v="",y={},w=32,b=null,T=!1,E=!1,O="[iFrameSizer]",S=O.length,M="",I={max:1,min:1,bodyScroll:1,documentElementScroll:1},N="child",A=!0,C=window.parent,z="*",k=0,R=!1,e=null,x=16,L=1,F="scroll",P=F,D=window,j=function(){ae("onMessage function not defined")},q=function(){},H=function(){},W={height:function(){return ae("Custom height calculation function not defined"),document.documentElement.offsetHeight},width:function(){return ae("Custom width calculation function not defined"),document.body.scrollWidth}},B={},J=!1;try{var U=Object.create({},{passive:{get:function(){J=!0}}});window.addEventListener("test",te,U),window.removeEventListener("test",te,U)}catch(e){}var V,X,Y,K,Q,G,Z=Date.now||function(){return(new Date).getTime()},$={bodyOffset:function(){return document.body.offsetHeight+ve("marginTop")+ve("marginBottom")},offset:function(){return $.bodyOffset()},bodyScroll:function(){return document.body.scrollHeight},custom:function(){return W.height()},documentElementOffset:function(){return document.documentElement.offsetHeight},documentElementScroll:function(){return document.documentElement.scrollHeight},max:function(){return Math.max.apply(null,we($))},min:function(){return Math.min.apply(null,we($))},grow:function(){return $.max()},lowestElement:function(){return Math.max($.bodyOffset()||$.documentElementOffset(),ye("bottom",Te()))},taggedElement:function(){return be("bottom","data-iframe-height")}},_={bodyScroll:function(){return document.body.scrollWidth},bodyOffset:function(){return document.body.offsetWidth},custom:function(){return W.width()},documentElementScroll:function(){return document.documentElement.scrollWidth},documentElementOffset:function(){return document.documentElement.offsetWidth},scroll:function(){return Math.max(_.bodyScroll(),_.documentElementScroll())},max:function(){return Math.max.apply(null,we(_))},min:function(){return Math.min.apply(null,we(_))},rightMostElement:function(){return ye("right",Te())},taggedElement:function(){return be("right","data-iframe-width")}},ee=(V=Ee,Q=null,G=0,function(){var e=Z(),t=x-(e-(G=G||e));return X=this,Y=arguments,t<=0||x void; +} + +const onInit = async () => { + postRobot.on( + "props", + { + window: window.parent, + domain: window.location.origin, + }, + (event) => { + // Typescript definition for event is wrong. So read props by casting to + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const props = (event as any).data as CellOutputViewerProps; + const outputs = ( +
    + {props.outputs?.map((output, index) => ( + + props.onMetadataChange(metadata, mediaType, index)} + /> + props.onMetadataChange(metadata, mediaType, index)} + /> + + + + ))} +
    + ); + + ReactDOM.render(outputs, document.getElementById("cellOutput")); + } + ); +}; + +// Entry point +window.addEventListener("load", onInit); diff --git a/src/CellOutputViewer/TransformMedia.tsx b/src/CellOutputViewer/TransformMedia.tsx new file mode 100644 index 000000000..b38922042 --- /dev/null +++ b/src/CellOutputViewer/TransformMedia.tsx @@ -0,0 +1,138 @@ +import { ImmutableDisplayData, ImmutableExecuteResult, JSONObject } from "@nteract/commutable"; +// import outputs individually to avoid increasing the bundle size +import { HTML } from "@nteract/outputs/lib/components/media/html"; +import { Image } from "@nteract/outputs/lib/components/media/image"; +import { JavaScript } from "@nteract/outputs/lib/components/media/javascript"; +import { Json } from "@nteract/outputs/lib/components/media/json"; +import { LaTeX } from "@nteract/outputs/lib/components/media/latex"; +import { Plain } from "@nteract/outputs/lib/components/media/plain"; +import { SVG } from "@nteract/outputs/lib/components/media/svg"; +import { ContentRef } from "@nteract/types"; +import React, { Suspense } from "react"; + +const EmptyTransform = (): JSX.Element => <>; + +const displayOrder = [ + "application/vnd.jupyter.widget-view+json", + "application/vnd.vega.v5+json", + "application/vnd.vega.v4+json", + "application/vnd.vega.v3+json", + "application/vnd.vega.v2+json", + "application/vnd.vegalite.v4+json", + "application/vnd.vegalite.v3+json", + "application/vnd.vegalite.v2+json", + "application/vnd.vegalite.v1+json", + "application/geo+json", + "application/vnd.plotly.v1+json", + "text/vnd.plotly.v1+html", + "application/x-nteract-model-debug+json", + "application/vnd.dataresource+json", + "application/vdom.v1+json", + "application/json", + "application/javascript", + "text/html", + "text/markdown", + "text/latex", + "image/svg+xml", + "image/gif", + "image/png", + "image/jpeg", + "text/plain", +]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformsById = new Map>([ + ["text/vnd.plotly.v1+html", React.lazy(() => import("@nteract/transform-plotly"))], + ["application/vnd.plotly.v1+json", React.lazy(() => import("@nteract/transform-plotly"))], + ["application/geo+json", EmptyTransform], // TODO: The geojson transform will likely need some work because of the basemap URL(s) + ["application/x-nteract-model-debug+json", React.lazy(() => import("@nteract/transform-model-debug"))], + ["application/vnd.dataresource+json", React.lazy(() => import("@nteract/data-explorer"))], + ["application/vnd.jupyter.widget-view+json", React.lazy(() => import("./transforms/WidgetDisplay"))], + ["application/vnd.vegalite.v1+json", React.lazy(() => import("./transforms/VegaLite1"))], + ["application/vnd.vegalite.v2+json", React.lazy(() => import("./transforms/VegaLite2"))], + ["application/vnd.vegalite.v3+json", React.lazy(() => import("./transforms/VegaLite3"))], + ["application/vnd.vegalite.v4+json", React.lazy(() => import("./transforms/VegaLite4"))], + ["application/vnd.vega.v2+json", React.lazy(() => import("./transforms/Vega2"))], + ["application/vnd.vega.v3+json", React.lazy(() => import("./transforms/Vega3"))], + ["application/vnd.vega.v4+json", React.lazy(() => import("./transforms/Vega4"))], + ["application/vnd.vega.v5+json", React.lazy(() => import("./transforms/Vega5"))], + ["application/vdom.v1+json", React.lazy(() => import("@nteract/transform-vdom"))], + ["application/json", Json], + ["application/javascript", JavaScript], + ["text/html", HTML], + ["text/markdown", React.lazy(() => import("@nteract/outputs/lib/components/media/markdown"))], // Markdown increases the bundle size so lazy load it + ["text/latex", LaTeX], + ["image/svg+xml", SVG], + ["image/gif", Image], + ["image/png", Image], + ["image/jpeg", Image], + ["text/plain", Plain], +]); + +interface TransformMediaProps { + output_type: string; + id: string; + contentRef: ContentRef; + output?: ImmutableDisplayData | ImmutableExecuteResult; + onMetadataChange: (metadata: JSONObject, mediaType: string) => void; +} + +export const TransformMedia = (props: TransformMediaProps): JSX.Element => { + const { Media, mediaType, data, metadata } = getMediaInfo(props); + + // If we had no valid result, return an empty output + if (!mediaType || !data) { + return <>; + } + + return ( + Loading...
    }> + + + ); +}; + +const getMediaInfo = (props: TransformMediaProps) => { + const { output, output_type } = props; + // This component should only be used with display data and execute result + if (!output || !(output_type === "display_data" || output_type === "execute_result")) { + console.warn("connected transform media managed to get a non media bundle output"); + return { + Media: EmptyTransform, + }; + } + + // Find the first mediaType in the output data that we support with a handler + const mediaType = displayOrder.find( + (key) => + Object.prototype.hasOwnProperty.call(output.data, key) && + (Object.prototype.hasOwnProperty.call(transformsById, key) || transformsById.get(key)) + ); + + if (mediaType) { + const metadata = output.metadata.get(mediaType); + const data = output.data[mediaType]; + + const Media = transformsById.get(mediaType); + return { + Media, + mediaType, + data, + metadata, + }; + } + + return { + Media: EmptyTransform, + mediaType, + output, + }; +}; + +export default TransformMedia; diff --git a/src/CellOutputViewer/cellOutputViewer.html b/src/CellOutputViewer/cellOutputViewer.html new file mode 100644 index 000000000..7db8c5958 --- /dev/null +++ b/src/CellOutputViewer/cellOutputViewer.html @@ -0,0 +1,12 @@ + + + + + + Cell Output Viewer + + + +
    + + diff --git a/src/CellOutputViewer/transforms/Vega2.ts b/src/CellOutputViewer/transforms/Vega2.ts new file mode 100644 index 000000000..08a358f54 --- /dev/null +++ b/src/CellOutputViewer/transforms/Vega2.ts @@ -0,0 +1 @@ +export { Vega2 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/Vega3.ts b/src/CellOutputViewer/transforms/Vega3.ts new file mode 100644 index 000000000..87289b52f --- /dev/null +++ b/src/CellOutputViewer/transforms/Vega3.ts @@ -0,0 +1 @@ +export { Vega3 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/Vega4.ts b/src/CellOutputViewer/transforms/Vega4.ts new file mode 100644 index 000000000..a95100d63 --- /dev/null +++ b/src/CellOutputViewer/transforms/Vega4.ts @@ -0,0 +1 @@ +export { Vega4 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/Vega5.ts b/src/CellOutputViewer/transforms/Vega5.ts new file mode 100644 index 000000000..6fa20b452 --- /dev/null +++ b/src/CellOutputViewer/transforms/Vega5.ts @@ -0,0 +1 @@ +export { Vega5 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite1.ts b/src/CellOutputViewer/transforms/VegaLite1.ts new file mode 100644 index 000000000..fb615d92c --- /dev/null +++ b/src/CellOutputViewer/transforms/VegaLite1.ts @@ -0,0 +1 @@ +export { VegaLite1 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite2.ts b/src/CellOutputViewer/transforms/VegaLite2.ts new file mode 100644 index 000000000..f664e0bda --- /dev/null +++ b/src/CellOutputViewer/transforms/VegaLite2.ts @@ -0,0 +1 @@ +export { VegaLite2 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite3.ts b/src/CellOutputViewer/transforms/VegaLite3.ts new file mode 100644 index 000000000..8a57abbc4 --- /dev/null +++ b/src/CellOutputViewer/transforms/VegaLite3.ts @@ -0,0 +1 @@ +export { VegaLite3 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite4.ts b/src/CellOutputViewer/transforms/VegaLite4.ts new file mode 100644 index 000000000..fca92c80f --- /dev/null +++ b/src/CellOutputViewer/transforms/VegaLite4.ts @@ -0,0 +1 @@ +export { VegaLite4 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/WidgetDisplay.ts b/src/CellOutputViewer/transforms/WidgetDisplay.ts new file mode 100644 index 000000000..4387a4f5d --- /dev/null +++ b/src/CellOutputViewer/transforms/WidgetDisplay.ts @@ -0,0 +1 @@ +export { WidgetDisplay as default } from "@nteract/jupyter-widgets"; diff --git a/src/Explorer/Notebook/NotebookClientV2.ts b/src/Explorer/Notebook/NotebookClientV2.ts index 940be1e3a..7c5f4179a 100644 --- a/src/Explorer/Notebook/NotebookClientV2.ts +++ b/src/Explorer/Notebook/NotebookClientV2.ts @@ -34,8 +34,6 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import configureStore from "./NotebookComponent/store"; import { CdbAppState, makeCdbRecord } from "./NotebookComponent/types"; -import SandboxJavaScript from "./NotebookRenderer/outputs/SandboxJavaScript"; -import SanitizedHTML from "./NotebookRenderer/outputs/SanitizedHTML"; export type KernelSpecsDisplay = { name: string; displayName: string }; @@ -125,62 +123,62 @@ export class NotebookClientV2 { contents: makeContentsRecord({ byRef: Immutable.Map(), }), - transforms: makeTransformsRecord({ - displayOrder: Immutable.List([ - "application/vnd.jupyter.widget-view+json", - "application/vnd.vega.v5+json", - "application/vnd.vega.v4+json", - "application/vnd.vega.v3+json", - "application/vnd.vega.v2+json", - "application/vnd.vegalite.v3+json", - "application/vnd.vegalite.v2+json", - "application/vnd.vegalite.v1+json", - "application/geo+json", - "application/vnd.plotly.v1+json", - "text/vnd.plotly.v1+html", - "application/x-nteract-model-debug+json", - "application/vnd.dataresource+json", - "application/vdom.v1+json", - "application/json", - "application/javascript", - "text/html", - "text/markdown", - "text/latex", - "image/svg+xml", - "image/gif", - "image/png", - "image/jpeg", - "text/plain", - ]), - byId: Immutable.Map({ - "text/vnd.plotly.v1+html": NullTransform, - "application/vnd.plotly.v1+json": NullTransform, - "application/geo+json": NullTransform, - "application/x-nteract-model-debug+json": NullTransform, - "application/vnd.dataresource+json": NullTransform, - "application/vnd.jupyter.widget-view+json": NullTransform, - "application/vnd.vegalite.v1+json": NullTransform, - "application/vnd.vegalite.v2+json": NullTransform, - "application/vnd.vegalite.v3+json": NullTransform, - "application/vnd.vega.v2+json": NullTransform, - "application/vnd.vega.v3+json": NullTransform, - "application/vnd.vega.v4+json": NullTransform, - "application/vnd.vega.v5+json": NullTransform, - "application/vdom.v1+json": TransformVDOM, - "application/json": Media.Json, - "application/javascript": userContext.features.sandboxNotebookOutputs - ? SandboxJavaScript - : Media.JavaScript, - "text/html": userContext.features.sandboxNotebookOutputs ? SanitizedHTML : Media.HTML, - "text/markdown": Media.Markdown, - "text/latex": Media.LaTeX, - "image/svg+xml": Media.SVG, - "image/gif": Media.Image, - "image/png": Media.Image, - "image/jpeg": Media.Image, - "text/plain": Media.Plain, - }), - }), + transforms: userContext.features.sandboxNotebookOutputs + ? undefined + : makeTransformsRecord({ + displayOrder: Immutable.List([ + "application/vnd.jupyter.widget-view+json", + "application/vnd.vega.v5+json", + "application/vnd.vega.v4+json", + "application/vnd.vega.v3+json", + "application/vnd.vega.v2+json", + "application/vnd.vegalite.v3+json", + "application/vnd.vegalite.v2+json", + "application/vnd.vegalite.v1+json", + "application/geo+json", + "application/vnd.plotly.v1+json", + "text/vnd.plotly.v1+html", + "application/x-nteract-model-debug+json", + "application/vnd.dataresource+json", + "application/vdom.v1+json", + "application/json", + "application/javascript", + "text/html", + "text/markdown", + "text/latex", + "image/svg+xml", + "image/gif", + "image/png", + "image/jpeg", + "text/plain", + ]), + byId: Immutable.Map({ + "text/vnd.plotly.v1+html": NullTransform, + "application/vnd.plotly.v1+json": NullTransform, + "application/geo+json": NullTransform, + "application/x-nteract-model-debug+json": NullTransform, + "application/vnd.dataresource+json": NullTransform, + "application/vnd.jupyter.widget-view+json": NullTransform, + "application/vnd.vegalite.v1+json": NullTransform, + "application/vnd.vegalite.v2+json": NullTransform, + "application/vnd.vegalite.v3+json": NullTransform, + "application/vnd.vega.v2+json": NullTransform, + "application/vnd.vega.v3+json": NullTransform, + "application/vnd.vega.v4+json": NullTransform, + "application/vnd.vega.v5+json": NullTransform, + "application/vdom.v1+json": TransformVDOM, + "application/json": Media.Json, + "application/javascript": Media.JavaScript, + "text/html": Media.HTML, + "text/markdown": Media.Markdown, + "text/latex": Media.LaTeX, + "image/svg+xml": Media.SVG, + "image/gif": Media.Image, + "image/png": Media.Image, + "image/jpeg": Media.Image, + "text/plain": Media.Plain, + }), + }), }), }), cdb: makeCdbRecord({ diff --git a/src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx b/src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx index 75133d7f3..ceef02cbb 100644 --- a/src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx +++ b/src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx @@ -1,10 +1,8 @@ import { actions, ContentRef } from "@nteract/core"; -import { KernelOutputError, StreamText } from "@nteract/outputs"; import { Cells, CodeCell, MarkdownCell, RawCell } from "@nteract/stateful-components"; import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor"; import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor"; import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt"; -import TransformMedia from "@nteract/stateful-components/lib/outputs/transform-media"; import * as React from "react"; import { connect } from "react-redux"; import { Dispatch } from "redux"; @@ -14,7 +12,7 @@ import { AzureTheme } from "./AzureTheme"; import "./base.css"; import "./default.css"; import "./NotebookReadOnlyRenderer.less"; -import IFrameOutputs from "./outputs/IFrameOutputs"; +import SandboxOutputs from "./outputs/SandboxOutputs"; export interface NotebookRendererProps { contentRef: any; @@ -27,7 +25,9 @@ export interface NotebookRendererProps { */ class NotebookReadOnlyRenderer extends React.Component { componentDidMount() { - loadTransform(this.props as any); + if (!userContext.features.sandboxNotebookOutputs) { + loadTransform(this.props as any); + } } private renderPrompt(id: string, contentRef: string): JSX.Element { @@ -63,14 +63,7 @@ class NotebookReadOnlyRenderer extends React.Component { {{ prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef), outputs: userContext.features.sandboxNotebookOutputs - ? (props: any) => ( - - - - - - - ) + ? () => : undefined, editor: { monaco: (props: PassedEditorProps) => diff --git a/src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx b/src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx index b78fdab97..876cbf337 100644 --- a/src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx +++ b/src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx @@ -1,11 +1,9 @@ import { CellId } from "@nteract/commutable"; import { CellType } from "@nteract/commutable/src"; import { actions, ContentRef } from "@nteract/core"; -import { KernelOutputError, StreamText } from "@nteract/outputs"; import { Cells, CodeCell, RawCell } from "@nteract/stateful-components"; import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor"; import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor"; -import TransformMedia from "@nteract/stateful-components/lib/outputs/transform-media"; import * as React from "react"; import { DndProvider } from "react-dnd"; import HTML5Backend from "react-dnd-html5-backend"; @@ -23,7 +21,7 @@ import KeyboardShortcuts from "./decorators/kbd-shortcuts"; import "./default.css"; import MarkdownCell from "./markdown-cell"; import "./NotebookRenderer.less"; -import IFrameOutputs from "./outputs/IFrameOutputs"; +import SandboxOutputs from "./outputs/SandboxOutputs"; import Prompt from "./Prompt"; import { promptContent } from "./PromptContent"; import StatusBar from "./StatusBar"; @@ -71,7 +69,9 @@ class BaseNotebookRenderer extends React.Component { } componentDidMount() { - loadTransform(this.props as any); + if (!userContext.features.sandboxNotebookOutputs) { + loadTransform(this.props as any); + } this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current); } @@ -109,14 +109,7 @@ class BaseNotebookRenderer extends React.Component { ), toolbar: () => , outputs: userContext.features.sandboxNotebookOutputs - ? (props: any) => ( - - - - - - - ) + ? () => : undefined, }} diff --git a/src/Explorer/Notebook/NotebookRenderer/outputs/IFrameOutputs.tsx b/src/Explorer/Notebook/NotebookRenderer/outputs/IFrameOutputs.tsx deleted file mode 100644 index 8ca9764f5..000000000 --- a/src/Explorer/Notebook/NotebookRenderer/outputs/IFrameOutputs.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AppState, ContentRef, selectors } from "@nteract/core"; -import { Output } from "@nteract/outputs"; -import Immutable from "immutable"; -import React from "react"; -import { connect } from "react-redux"; -import { SandboxFrame } from "./SandboxFrame"; - -// Adapted from https://github.com/nteract/nteract/blob/main/packages/stateful-components/src/outputs/index.tsx -// to add support for sandboxing using - ); - } - - componentWillUnmount(): void { - this.resizeObserver?.disconnect(); - this.mutationObserver?.disconnect(); - } - - onFrameLoad(event: React.SyntheticEvent): void { - const doc = (event.target as HTMLIFrameElement).contentDocument; - copyStyles(document, doc); - - this.setState({ frameBody: doc.body }); - - this.mutationObserver = new MutationObserver(() => { - const bodyFirstElementChild = this.state.frameBody?.firstElementChild; - if (!this.resizeObserver && bodyFirstElementChild) { - this.resizeObserver = new ResizeObserver(() => - this.setState({ - frameHeight: this.state.frameBody?.firstElementChild.scrollHeight, - }) - ); - this.resizeObserver.observe(bodyFirstElementChild); - } - }); - this.mutationObserver.observe(doc.body, { childList: true }); - } -} diff --git a/src/Explorer/Notebook/NotebookRenderer/outputs/SandboxJavaScript.tsx b/src/Explorer/Notebook/NotebookRenderer/outputs/SandboxJavaScript.tsx deleted file mode 100644 index c6420a30e..000000000 --- a/src/Explorer/Notebook/NotebookRenderer/outputs/SandboxJavaScript.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Media } from "@nteract/outputs"; -import React from "react"; - -interface Props { - /** - * The JavaScript code that we would like to execute. - */ - data: string; - /** - * The media type associated with our component. - */ - mediaType: "text/javascript"; -} - -export class SandboxJavaScript extends React.PureComponent { - static defaultProps = { - data: "", - mediaType: "application/javascript", - }; - - render(): JSX.Element { - return ${this.props.data}`} />; - } -} - -export default SandboxJavaScript; diff --git a/src/Explorer/Notebook/NotebookRenderer/outputs/SandboxOutputs.tsx b/src/Explorer/Notebook/NotebookRenderer/outputs/SandboxOutputs.tsx new file mode 100644 index 000000000..40f42fd35 --- /dev/null +++ b/src/Explorer/Notebook/NotebookRenderer/outputs/SandboxOutputs.tsx @@ -0,0 +1,132 @@ +import { JSONObject } from "@nteract/commutable"; +import { outputToJS } from "@nteract/commutable/lib/v4"; +import { actions, AppState, ContentRef, selectors } from "@nteract/core"; +import IframeResizer from "iframe-resizer-react"; +import Immutable from "immutable"; +import postRobot from "post-robot"; +import React from "react"; +import { connect } from "react-redux"; +import { Dispatch } from "redux"; +import { CellOutputViewerProps } from "../../../../CellOutputViewer/CellOutputViewer"; + +// Adapted from https://github.com/nteract/nteract/blob/main/packages/stateful-components/src/outputs/index.tsx +// to add support for sandboxing using