From f5da8bb276c343c3ebee293ccdfa195268d78bdb Mon Sep 17 00:00:00 2001 From: Armando Trejo Oliver Date: Mon, 24 Jan 2022 13:06:43 -0800 Subject: [PATCH 1/2] Validate endpoints from feature flags (#1196) Validate endpoints from feature flags --- .vscode/launch.json | 6 +- src/Common/MongoProxyClient.test.ts | 7 +-- src/Common/MongoProxyClient.ts | 16 +++-- src/ConfigContext.ts | 86 ++++++++++++++++++-------- src/Explorer/Explorer.tsx | 29 ++++----- src/Juno/JunoClient.ts | 3 +- src/Phoenix/PhoenixClient.ts | 3 +- src/Platform/Hosted/extractFeatures.ts | 2 - src/Utils/EndpointValidation.ts | 83 +++++++++++++++++++++++++ src/Utils/MessageValidation.ts | 2 +- 10 files changed, 178 insertions(+), 59 deletions(-) create mode 100644 src/Utils/EndpointValidation.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 46e9150b3..bcb47a377 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,8 @@ "--inspect-brk", "${workspaceRoot}/node_modules/jest/bin/jest.js", "--runInBand", - "--coverage", "false" + "--coverage", + "false" ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", @@ -26,7 +27,8 @@ "--inspect-brk", "${workspaceRoot}/node_modules/jest/bin/jest.js", "${fileBasenameNoExtension}", - "--coverage", "false", + "--coverage", + "false", // "--watch", // // --no-cache only used to make --watch work. Otherwise jest ignores the breakpoints. // // https://github.com/facebook/jest/issues/6683 diff --git a/src/Common/MongoProxyClient.test.ts b/src/Common/MongoProxyClient.test.ts index 1c49141a0..7edae316b 100644 --- a/src/Common/MongoProxyClient.test.ts +++ b/src/Common/MongoProxyClient.test.ts @@ -236,13 +236,12 @@ describe("MongoProxyClient", () => { }); it("returns a production endpoint", () => { - const endpoint = getEndpoint(); + const endpoint = getEndpoint("https://main.documentdb.ext.azure.com"); expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer"); }); it("returns a development endpoint", () => { - updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); - const endpoint = getEndpoint(); + const endpoint = getEndpoint("https://localhost:1234"); expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer"); }); @@ -250,7 +249,7 @@ describe("MongoProxyClient", () => { updateUserContext({ authType: AuthType.EncryptedToken, }); - const endpoint = getEndpoint(); + const endpoint = getEndpoint("https://main.documentdb.ext.azure.com"); expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); }); }); diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 668a0ab16..1a0c75a4a 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -1,5 +1,6 @@ import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import queryString from "querystring"; +import { allowedMongoProxyEndpoints, validateEndpoint } from "Utils/EndpointValidation"; import { AuthType } from "../AuthType"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; @@ -336,14 +337,17 @@ export function createMongoCollectionWithProxy( } export function getFeatureEndpointOrDefault(feature: string): string { - return hasFlag(userContext.features.mongoProxyAPIs, feature) - ? getEndpoint(userContext.features.mongoProxyEndpoint) - : getEndpoint(); + const endpoint = + hasFlag(userContext.features.mongoProxyAPIs, feature) && + validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints) + ? userContext.features.mongoProxyEndpoint + : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; + + return getEndpoint(endpoint); } -export function getEndpoint(customEndpoint?: string): string { - let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; - url += "/api/mongo/explorer"; +export function getEndpoint(endpoint: string): string { + let url = endpoint + "/api/mongo/explorer"; if (userContext.authType === AuthType.EncryptedToken) { url = url.replace("api/mongo", "api/guest/mongo"); diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 61780c128..d63301a61 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -1,4 +1,16 @@ -import { JunoEndpoints } from "Common/Constants"; +import { + allowedAadEndpoints, + allowedArcadiaEndpoints, + allowedArmEndpoints, + allowedBackendEndpoints, + allowedEmulatorEndpoints, + allowedGraphEndpoints, + allowedHostedExplorerEndpoints, + allowedJunoOrigins, + allowedMongoBackendEndpoints, + allowedMsalRedirectEndpoints, + validateEndpoint, +} from "Utils/EndpointValidation"; export enum Platform { Portal = "Portal", @@ -8,7 +20,7 @@ export enum Platform { export interface ConfigContext { platform: Platform; - allowedParentFrameOrigins: string[]; + allowedParentFrameOrigins: ReadonlyArray; gitSha?: string; proxyPath?: string; AAD_ENDPOINT: string; @@ -30,7 +42,6 @@ export interface ConfigContext { isTerminalEnabled: boolean; hostedExplorerURL: string; armAPIVersion?: string; - allowedJunoOrigins: string[]; msalRedirectURI?: string; } @@ -44,8 +55,7 @@ let configContext: Readonly = { `^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`, `^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`, `^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`, - ], - // Webpack injects this at build time + ], // Webpack injects this at build time gitSha: process.env.GIT_SHA, hostedExplorerURL: "https://cosmos.azure.com/", AAD_ENDPOINT: "https://login.microsoftonline.com/", @@ -61,14 +71,6 @@ let configContext: Readonly = { JUNO_ENDPOINT: "https://tools.cosmos.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", isTerminalEnabled: false, - allowedJunoOrigins: [ - JunoEndpoints.Test, - JunoEndpoints.Test2, - JunoEndpoints.Test3, - JunoEndpoints.Prod, - JunoEndpoints.Stage, - "https://localhost", - ], }; export function resetConfigContext(): void { @@ -79,6 +81,50 @@ export function resetConfigContext(): void { } export function updateConfigContext(newContext: Partial): void { + if (!newContext) { + return; + } + + if (!validateEndpoint(newContext.ARM_ENDPOINT, allowedArmEndpoints)) { + delete newContext.ARM_ENDPOINT; + } + + if (!validateEndpoint(newContext.AAD_ENDPOINT, allowedAadEndpoints)) { + delete newContext.AAD_ENDPOINT; + } + + if (!validateEndpoint(newContext.EMULATOR_ENDPOINT, allowedEmulatorEndpoints)) { + delete newContext.EMULATOR_ENDPOINT; + } + + if (!validateEndpoint(newContext.GRAPH_ENDPOINT, allowedGraphEndpoints)) { + delete newContext.GRAPH_ENDPOINT; + } + + if (!validateEndpoint(newContext.ARCADIA_ENDPOINT, allowedArcadiaEndpoints)) { + delete newContext.ARCADIA_ENDPOINT; + } + + if (!validateEndpoint(newContext.BACKEND_ENDPOINT, allowedBackendEndpoints)) { + delete newContext.BACKEND_ENDPOINT; + } + + if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) { + delete newContext.MONGO_BACKEND_ENDPOINT; + } + + if (!validateEndpoint(newContext.JUNO_ENDPOINT, allowedJunoOrigins)) { + delete newContext.JUNO_ENDPOINT; + } + + if (!validateEndpoint(newContext.hostedExplorerURL, allowedHostedExplorerEndpoints)) { + delete newContext.hostedExplorerURL; + } + + if (!validateEndpoint(newContext.msalRedirectURI, allowedMsalRedirectEndpoints)) { + delete newContext.msalRedirectURI; + } + Object.assign(configContext, newContext); } @@ -102,18 +148,8 @@ export async function initializeConfiguration(): Promise { }); if (response.status === 200) { try { - 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], - }); - } + const { ...externalConfig } = await response.json(); + updateConfigContext(externalConfig); } catch (error) { console.error("Unable to parse json in config file"); console.error(error); diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 730bcd9e3..08e9e8c98 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1,8 +1,10 @@ import { Link } from "@fluentui/react/lib/Link"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; +import { IGalleryItem } from "Juno/JunoClient"; import * as ko from "knockout"; import React from "react"; import _ from "underscore"; +import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation"; import shallow from "zustand/shallow"; import { AuthType } from "../AuthType"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; @@ -24,7 +26,6 @@ import * as ViewModels from "../Contracts/ViewModels"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { useSidePanel } from "../hooks/useSidePanel"; import { useTabs } from "../hooks/useTabs"; -import { IGalleryItem } from "../Juno/JunoClient"; import { PhoenixClient } from "../Phoenix/PhoenixClient"; import * as ExplorerSettings from "../Shared/ExplorerSettings"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; @@ -50,7 +51,7 @@ import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import { SnapshotRequest } from "./Notebook/NotebookComponent/types"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import type NotebookManager from "./Notebook/NotebookManager"; -import type { NotebookPaneContent } from "./Notebook/NotebookManager"; +import { NotebookPaneContent } from "./Notebook/NotebookManager"; import { NotebookUtil } from "./Notebook/NotebookUtil"; import { useNotebook } from "./Notebook/useNotebook"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; @@ -178,7 +179,11 @@ export default class Explorer { this.resourceTree = new ResourceTreeAdapter(this); // Override notebook server parameters from URL parameters - if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) { + if ( + userContext.features.notebookServerUrl && + validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) && + userContext.features.notebookServerToken + ) { useNotebook.getState().setNotebookServerInfo({ notebookServerEndpoint: userContext.features.notebookServerUrl, authToken: userContext.features.notebookServerToken, @@ -190,19 +195,6 @@ export default class Explorer { useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath); } - if (userContext.features.livyEndpoint) { - useNotebook.getState().setSparkClusterConnectionInfo({ - userName: undefined, - password: undefined, - endpoints: [ - { - endpoint: userContext.features.livyEndpoint, - kind: DataModels.SparkClusterEndpointKind.Livy, - }, - ], - }); - } - this.refreshExplorer(); } @@ -422,7 +414,10 @@ export default class Explorer { connectionStatus.status = ConnectionStatusType.Connected; useNotebook.getState().setConnectionInfo(connectionStatus); useNotebook.getState().setNotebookServerInfo({ - notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl, + notebookServerEndpoint: + (validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) && + userContext.features.notebookServerUrl) || + connectionInfo.data.notebookServerUrl, authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken, forwardingId: connectionInfo.data.forwardingId, }); diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts index 540c339e8..8296500cc 100644 --- a/src/Juno/JunoClient.ts +++ b/src/Juno/JunoClient.ts @@ -1,4 +1,5 @@ import ko from "knockout"; +import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation"; import { GetGithubClientId } from "Utils/GitHubUtils"; import { HttpHeaders, HttpStatusCodes } from "../Common/Constants"; import { configContext } from "../ConfigContext"; @@ -484,7 +485,7 @@ 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) { + if (!validateEndpoint(junoEndpoint, allowedJunoOrigins)) { const error = `${junoEndpoint} not allowed as juno endpoint`; console.error(error); throw new Error(error); diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts index 55bd8a226..e703f48e5 100644 --- a/src/Phoenix/PhoenixClient.ts +++ b/src/Phoenix/PhoenixClient.ts @@ -1,5 +1,6 @@ import promiseRetry, { AbortError } from "p-retry"; import { Action } from "Shared/Telemetry/TelemetryConstants"; +import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation"; import { Areas, ConnectionStatusType, @@ -154,7 +155,7 @@ export class PhoenixClient { public static getPhoenixEndpoint(): string { const phoenixEndpoint = userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; - if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) { + if (!validateEndpoint(phoenixEndpoint, allowedJunoOrigins)) { const error = `${phoenixEndpoint} not allowed as juno endpoint`; console.error(error); throw new Error(error); diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 1f17d0aa9..4b498c03b 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -23,7 +23,6 @@ export type Features = { readonly hostedDataExplorer: boolean; readonly junoEndpoint?: string; readonly phoenixEndpoint?: string; - readonly livyEndpoint?: string; readonly notebookBasePath?: string; readonly notebookServerToken?: string; readonly notebookServerUrl?: string; @@ -72,7 +71,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear mongoProxyAPIs: get("mongoproxyapis"), junoEndpoint: get("junoendpoint"), phoenixEndpoint: get("phoenixendpoint"), - livyEndpoint: get("livyendpoint"), notebookBasePath: get("notebookbasepath"), notebookServerToken: get("notebookservertoken"), notebookServerUrl: get("notebookserverurl"), diff --git a/src/Utils/EndpointValidation.ts b/src/Utils/EndpointValidation.ts new file mode 100644 index 000000000..0e3479d97 --- /dev/null +++ b/src/Utils/EndpointValidation.ts @@ -0,0 +1,83 @@ +import { JunoEndpoints } from "Common/Constants"; +import * as Logger from "../Common/Logger"; + +export function validateEndpoint( + endpointToValidate: string | undefined, + allowedEndpoints: ReadonlyArray +): boolean { + try { + return validateEndpointInternal( + endpointToValidate, + allowedEndpoints.map((e) => e) + ); + } catch (reason) { + Logger.logError(`${endpointToValidate} not allowed`, "validateEndpoint"); + Logger.logError(`${JSON.stringify(reason)}`, "validateEndpoint"); + return false; + } +} + +function validateEndpointInternal( + endpointToValidate: string | undefined, + allowedEndpoints: ReadonlyArray +): boolean { + if (endpointToValidate === undefined) { + return false; + } + + const originToValidate: string = new URL(endpointToValidate).origin; + const allowedOrigins: string[] = allowedEndpoints.map((allowedEndpoint) => new URL(allowedEndpoint).origin) || []; + const valid = allowedOrigins.indexOf(originToValidate) >= 0; + + if (!valid) { + throw new Error( + `${endpointToValidate} is not an allowed endpoint. Allowed endpoints are ${allowedArmEndpoints.toString()}` + ); + } + + return valid; +} + +export const allowedArmEndpoints: ReadonlyArray = [ + "https://​management.azure.com", + "https://​management.usgovcloudapi.net", + "https://management.chinacloudapi.cn", +]; + +export const allowedAadEndpoints: ReadonlyArray = ["https://login.microsoftonline.com/"]; + +export const allowedBackendEndpoints: ReadonlyArray = [ + "https://main.documentdb.ext.azure.com", + "https://localhost:12901", + "https://localhost:1234", +]; + +export const allowedMongoProxyEndpoints: ReadonlyArray = [ + "https://main.documentdb.ext.azure.com", + "https://localhost:12901", +]; + +export const allowedEmulatorEndpoints: ReadonlyArray = ["https://localhost:8081"]; + +export const allowedMongoBackendEndpoints: ReadonlyArray = ["https://localhost:1234"]; + +export const allowedGraphEndpoints: ReadonlyArray = ["https://graph.windows.net"]; + +export const allowedArcadiaEndpoints: ReadonlyArray = ["https://workspaceartifacts.projectarcadia.net"]; + +export const allowedHostedExplorerEndpoints: ReadonlyArray = ["https://cosmos.azure.com/"]; + +export const allowedMsalRedirectEndpoints: ReadonlyArray = [ + "https://cosmos-explorer-preview.azurewebsites.net/", +]; + +export const allowedJunoOrigins: ReadonlyArray = [ + JunoEndpoints.Test, + JunoEndpoints.Test2, + JunoEndpoints.Test3, + JunoEndpoints.Prod, + JunoEndpoints.Stage, + "https://localhost", +]; + +export const allowedNotebookServerUrls: ReadonlyArray = []; diff --git a/src/Utils/MessageValidation.ts b/src/Utils/MessageValidation.ts index 06aaee206..891c06369 100644 --- a/src/Utils/MessageValidation.ts +++ b/src/Utils/MessageValidation.ts @@ -4,7 +4,7 @@ export function isInvalidParentFrameOrigin(event: MessageEvent): boolean { return !isValidOrigin(configContext.allowedParentFrameOrigins, event); } -function isValidOrigin(allowedOrigins: string[], event: MessageEvent): boolean { +function isValidOrigin(allowedOrigins: ReadonlyArray, event: MessageEvent): boolean { const eventOrigin = (event && event.origin) || ""; const windowOrigin = (window && window.origin) || ""; if (eventOrigin === windowOrigin) { From 9358fd58895c67feaa73abcabedcb5008dff5b92 Mon Sep 17 00:00:00 2001 From: Karthik chakravarthy <88904658+kcheekuri@users.noreply.github.com> Date: Wed, 26 Jan 2022 07:31:38 -0500 Subject: [PATCH 2/2] Clean computeV2 code (#1194) Cleans compute V2 code used in the Phoenix notebooks flow. Fix the issue with 'Setup Notebooks' in quick start menu. --- src/Explorer/Explorer.tsx | 50 +- .../CommandBarComponentButtonFactory.tsx | 54 - .../SetupNotebooksPanel.test.tsx | 50 - .../SetupNotebooksPanel.tsx | 121 -- .../SetupNotebooksPanel.test.tsx.snap | 1773 ----------------- 5 files changed, 4 insertions(+), 2044 deletions(-) delete mode 100644 src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx delete mode 100644 src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx delete mode 100644 src/Explorer/Panes/SetupNotebooksPanel/__snapshots__/SetupNotebooksPanel.test.tsx.snap diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 08e9e8c98..6fc0d5f80 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -33,11 +33,7 @@ import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../UserContext"; import { getCollectionName, getUploadName } from "../Utils/APITypeUtils"; import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; -import { - get as getWorkspace, - listByDatabaseAccount, - start, -} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; +import { listByDatabaseAccount } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; import { stringToBlob } from "../Utils/BlobUtils"; import { isCapabilityEnabled } from "../Utils/CapabilityUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; @@ -57,7 +53,6 @@ import { useNotebook } from "./Notebook/useNotebook"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; -import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { StringInputPane } from "./Panes/StringInputPane/StringInputPane"; import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane"; import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane"; @@ -467,35 +462,6 @@ export default class Explorer { } } - private async ensureNotebookWorkspaceRunning() { - if (!userContext.databaseAccount) { - return; - } - - let clearMessage; - try { - const notebookWorkspace = await getWorkspace( - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount.name, - "default" - ); - if ( - notebookWorkspace && - notebookWorkspace.properties && - notebookWorkspace.properties.status && - notebookWorkspace.properties.status.toLowerCase() === "stopped" - ) { - clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace"); - await start(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default"); - } - } catch (error) { - handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace"); - } finally { - clearMessage && clearMessage(); - } - } - private _resetNotebookWorkspace = async () => { useDialog.getState().closeDialog(); const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace"); @@ -1182,20 +1148,12 @@ export default class Explorer { } } - private _openSetupNotebooksPaneForQuickstart(): void { - const title = "Enable Notebooks (Preview)"; - 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."; - useSidePanel - .getState() - .openSidePanel(title, ); - } - public async handleOpenFileAction(path: string): Promise { + if (useNotebook.getState().isPhoenixNotebooks === undefined) { + await useNotebook.getState().getPhoenixStatus(); + } if (useNotebook.getState().isPhoenixNotebooks) { await this.allocateContainer(); - } else if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) { - this._openSetupNotebooksPaneForQuickstart(); } // We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 0780f9e61..c4fa1b79c 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -10,7 +10,6 @@ import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg"; import GitHubIcon from "../../../../images/github.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; -import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg"; import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg"; @@ -35,7 +34,6 @@ import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPa import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane"; -import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { useDatabases } from "../../useDatabases"; import { SelectedNodeState } from "../../useSelectedNode"; @@ -111,11 +109,6 @@ export function createStaticCommandBarButtons( } buttons.push(btn); }); - } else { - if (!isRunningOnNationalCloud() && useNotebook.getState().isPhoenixNotebooks) { - buttons.push(createDivider()); - buttons.push(createEnableNotebooksButton(container)); - } } if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) { @@ -466,33 +459,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { }; } -function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps { - if (configContext.platform === Platform.Emulator) { - return undefined; - } - const label = "Enable Notebooks (Preview)"; - const tooltip = - "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const description = - "Looks like 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."; - return { - iconSrc: EnableNotebooksIcon, - iconAlt: label, - onCommandClick: () => - useSidePanel - .getState() - .openSidePanel( - label, - - ), - commandButtonLabel: label, - hasPopup: false, - disabled: !useNotebook.getState().isNotebooksEnabledForAccount, - ariaLabel: label, - tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip, - }; -} - function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { const label = "Open Terminal"; return { @@ -510,9 +476,6 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon const label = "Open Mongo Shell"; const tooltip = "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const title = "Set up workspace"; - const description = - "Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account."; const disableButton = !useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled; return { @@ -521,13 +484,6 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon onCommandClick: () => { if (useNotebook.getState().isNotebookEnabled) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); - } else { - useSidePanel - .getState() - .openSidePanel( - title, - - ); } }, commandButtonLabel: label, @@ -542,9 +498,6 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo const label = "Open Cassandra Shell"; const tooltip = "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const title = "Set up workspace"; - const description = - "Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account."; const disableButton = !useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled; return { @@ -553,13 +506,6 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo onCommandClick: () => { if (useNotebook.getState().isNotebookEnabled) { container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); - } else { - useSidePanel - .getState() - .openSidePanel( - title, - - ); } }, commandButtonLabel: label, diff --git a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx deleted file mode 100644 index bee2b9e6d..000000000 --- a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { PrimaryButton } from "@fluentui/react"; -import { mount } from "enzyme"; -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 deleted file mode 100644 index a7d943749..000000000 --- a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { PrimaryButton } from "@fluentui/react"; -import { useBoolean } from "@fluentui/react-hooks"; -import React, { FunctionComponent, KeyboardEvent, useState } from "react"; -import { Areas, NormalizedEventKey } from "../../../Common/Constants"; -import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; -import { useSidePanel } from "../../../hooks/useSidePanel"; -import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; -import { userContext } from "../../../UserContext"; -import { createOrUpdate } from "../../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import Explorer from "../../Explorer"; -import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; -import { PanelLoadingScreen } from "../PanelLoadingScreen"; -interface SetupNoteBooksPanelProps { - explorer: Explorer; - panelTitle: string; - panelDescription: string; -} - -export const SetupNoteBooksPanel: FunctionComponent = ({ - explorer, - panelTitle, - panelDescription, -}: SetupNoteBooksPanelProps): JSX.Element => { - const closeSidePanel = useSidePanel((state) => state.closeSidePanel); - - 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: panelTitle, - }); - - const clear = NotificationConsoleUtils.logConsoleProgress("Creating a new default notebook workspace"); - - try { - setLoadingTrue(); - await createOrUpdate( - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount.name, - "default" - ); - explorer.refreshExplorer(); - - closeSidePanel(); - - TelemetryProcessor.traceSuccess( - Action.CreateNotebookWorkspace, - { - dataExplorerArea: Areas.ContextualPane, - paneTitle: panelTitle, - }, - 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: panelTitle, - 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 deleted file mode 100644 index 25914ed75..000000000 --- a/src/Explorer/Panes/SetupNotebooksPanel/__snapshots__/SetupNotebooksPanel.test.tsx.snap +++ /dev/null @@ -1,1773 +0,0 @@ -// 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", - "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", - }, - }, - }, - "splitButtonMenuFocused": 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", - }, - }, - }, - "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" - > - - - - - - - -
-
- - -`;