mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-26 13:14:04 +00:00
Compare commits
1 Commits
v-yiqcao/n
...
users/tami
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acdd05a79b |
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -156,7 +156,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
npm start &
|
npm start &
|
||||||
node utils/cleanupDBs.js
|
|
||||||
npm run wait-for-server
|
npm run wait-for-server
|
||||||
npm run test:e2e
|
npm run test:e2e
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
4249
package-lock.json
generated
4249
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -10,10 +10,10 @@
|
|||||||
"@azure/identity": "1.2.1",
|
"@azure/identity": "1.2.1",
|
||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
"@jupyterlab/services": "6.0.2",
|
"@jupyterlab/services": "6.0.0-rc.2",
|
||||||
"@jupyterlab/terminal": "3.0.3",
|
"@jupyterlab/terminal": "3.0.0-rc.2",
|
||||||
"@microsoft/applicationinsights-web": "2.5.9",
|
"@microsoft/applicationinsights-web": "2.5.9",
|
||||||
"@nteract/commutable": "7.4.2",
|
"@nteract/commutable": "7.3.2",
|
||||||
"@nteract/connected-components": "6.8.2",
|
"@nteract/connected-components": "6.8.2",
|
||||||
"@nteract/core": "15.1.0",
|
"@nteract/core": "15.1.0",
|
||||||
"@nteract/data-explorer": "8.0.3",
|
"@nteract/data-explorer": "8.0.3",
|
||||||
@@ -64,9 +64,6 @@
|
|||||||
"eslint-plugin-react": "7.20.0",
|
"eslint-plugin-react": "7.20.0",
|
||||||
"hasher": "1.2.0",
|
"hasher": "1.2.0",
|
||||||
"html2canvas": "1.0.0-rc.5",
|
"html2canvas": "1.0.0-rc.5",
|
||||||
"i18next": "19.8.4",
|
|
||||||
"i18next-browser-languagedetector": "6.0.1",
|
|
||||||
"i18next-http-backend": "1.0.23",
|
|
||||||
"immutable": "4.0.0-rc.12",
|
"immutable": "4.0.0-rc.12",
|
||||||
"is-ci": "2.0.0",
|
"is-ci": "2.0.0",
|
||||||
"jquery": "3.5.1",
|
"jquery": "3.5.1",
|
||||||
@@ -89,7 +86,6 @@
|
|||||||
"react-dnd-html5-backend": "9.4.0",
|
"react-dnd-html5-backend": "9.4.0",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-i18next": "11.8.5",
|
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
@@ -128,7 +124,7 @@
|
|||||||
"@types/prop-types": "15.5.8",
|
"@types/prop-types": "15.5.8",
|
||||||
"@types/puppeteer": "3.0.1",
|
"@types/puppeteer": "3.0.1",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "16.9.56",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "17.0.0",
|
||||||
"@types/react-notification-system": "0.2.39",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
TriggerDefinition,
|
TriggerDefinition,
|
||||||
UserDefinedFunctionDefinition,
|
UserDefinedFunctionDefinition,
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
|
import Q from "q";
|
||||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
@@ -108,7 +109,7 @@ export interface CollectionBase extends TreeNode {
|
|||||||
|
|
||||||
onDocumentDBDocumentsClick(): void;
|
onDocumentDBDocumentsClick(): void;
|
||||||
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
|
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
|
||||||
expandCollection(): void;
|
expandCollection(): Q.Promise<any>;
|
||||||
collapseCollection(): void;
|
collapseCollection(): void;
|
||||||
getDatabase(): Database;
|
getDatabase(): Database;
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ export interface Collection extends CollectionBase {
|
|||||||
|
|
||||||
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
uploadFiles(fileList: FileList): Promise<UploadDetails>;
|
uploadFiles(fileList: FileList): Q.Promise<UploadDetails>;
|
||||||
|
|
||||||
getLabel(): string;
|
getLabel(): string;
|
||||||
}
|
}
|
||||||
@@ -293,7 +294,7 @@ export interface DocumentsTabOptions extends TabOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsTabV2Options extends TabOptions {
|
export interface SettingsTabV2Options extends TabOptions {
|
||||||
getPendingNotification: Promise<DataModels.Notification>;
|
getPendingNotification: Q.Promise<DataModels.Notification>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConflictsTabOptions extends TabOptions {
|
export interface ConflictsTabOptions extends TabOptions {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponent
|
|||||||
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
||||||
|
|
||||||
// Collection Tabs
|
// Collection Tabs
|
||||||
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
|
ko.components.register("documents-tab", new TabComponents.MongoDocumentsTabV2());
|
||||||
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
|
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
|
||||||
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
|
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
|
||||||
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
|
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
|||||||
}));
|
}));
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import Q from "q";
|
||||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer),
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer),
|
||||||
}));
|
}));
|
||||||
@@ -46,7 +47,9 @@ describe("SettingsComponent", () => {
|
|||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: undefined,
|
onUpdateTabsButtons: undefined,
|
||||||
getPendingNotification: Promise.resolve(undefined),
|
getPendingNotification: Q.Promise<DataModels.Notification>(() => {
|
||||||
|
return;
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ describe("SmartUiComponent", () => {
|
|||||||
root: {
|
root: {
|
||||||
id: "root",
|
id: "root",
|
||||||
info: {
|
info: {
|
||||||
messageTKey: "Start at $24/mo per database",
|
message: "Start at $24/mo per database",
|
||||||
link: {
|
link: {
|
||||||
href: "https://aka.ms/azure-cosmos-db-pricing",
|
href: "https://aka.ms/azure-cosmos-db-pricing",
|
||||||
textTKey: "More Details",
|
text: "More Details",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
@@ -21,10 +21,10 @@ describe("SmartUiComponent", () => {
|
|||||||
dataFieldName: "description",
|
dataFieldName: "description",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: {
|
description: {
|
||||||
textTKey: "this is an example description text.",
|
text: "this is an example description text.",
|
||||||
link: {
|
link: {
|
||||||
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
||||||
textTKey: "Click here for more information.",
|
text: "Click here for more information.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -32,7 +32,7 @@ describe("SmartUiComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "throughput",
|
id: "throughput",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Throughput (input)",
|
label: "Throughput (input)",
|
||||||
dataFieldName: "throughput",
|
dataFieldName: "throughput",
|
||||||
type: "number",
|
type: "number",
|
||||||
min: 400,
|
min: 400,
|
||||||
@@ -45,7 +45,7 @@ describe("SmartUiComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "throughput2",
|
id: "throughput2",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Throughput (Slider)",
|
label: "Throughput (Slider)",
|
||||||
dataFieldName: "throughput2",
|
dataFieldName: "throughput2",
|
||||||
type: "number",
|
type: "number",
|
||||||
min: 400,
|
min: 400,
|
||||||
@@ -58,7 +58,7 @@ describe("SmartUiComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "throughput3",
|
id: "throughput3",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Throughput (invalid)",
|
label: "Throughput (invalid)",
|
||||||
dataFieldName: "throughput3",
|
dataFieldName: "throughput3",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
min: 400,
|
min: 400,
|
||||||
@@ -72,7 +72,7 @@ describe("SmartUiComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "containerId",
|
id: "containerId",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Container id",
|
label: "Container id",
|
||||||
dataFieldName: "containerId",
|
dataFieldName: "containerId",
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
@@ -80,9 +80,9 @@ describe("SmartUiComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "analyticalStore",
|
id: "analyticalStore",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Analytical Store",
|
label: "Analytical Store",
|
||||||
trueLabelTKey: "Enabled",
|
trueLabel: "Enabled",
|
||||||
falseLabelTKey: "Disabled",
|
falseLabel: "Disabled",
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
dataFieldName: "analyticalStore",
|
dataFieldName: "analyticalStore",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
@@ -91,7 +91,7 @@ describe("SmartUiComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "database",
|
id: "database",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Database",
|
label: "Database",
|
||||||
dataFieldName: "database",
|
dataFieldName: "database",
|
||||||
type: "object",
|
type: "object",
|
||||||
choices: [
|
choices: [
|
||||||
@@ -117,9 +117,6 @@ describe("SmartUiComponent", () => {
|
|||||||
onError={() => {
|
onError={() => {
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
getTranslation={(key: string) => {
|
|
||||||
return key;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
@@ -148,9 +145,6 @@ describe("SmartUiComponent", () => {
|
|||||||
onError={() => {
|
onError={() => {
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
getTranslation={(key: string) => {
|
|
||||||
return key;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
NumberUiType,
|
NumberUiType,
|
||||||
SmartUiInput,
|
SmartUiInput,
|
||||||
} from "../../../SelfServe/SelfServeTypes";
|
} from "../../../SelfServe/SelfServeTypes";
|
||||||
import { TFunction } from "i18next";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic UX renderer
|
* Generic UX renderer
|
||||||
@@ -35,8 +34,8 @@ interface BaseDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BaseInput extends BaseDisplay {
|
interface BaseInput extends BaseDisplay {
|
||||||
labelTKey: string;
|
label: string;
|
||||||
placeholderTKey?: string;
|
placeholder?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +51,8 @@ interface NumberInput extends BaseInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BooleanInput extends BaseInput {
|
interface BooleanInput extends BaseInput {
|
||||||
trueLabelTKey: string;
|
trueLabel: string;
|
||||||
falseLabelTKey: string;
|
falseLabel: string;
|
||||||
defaultValue?: boolean;
|
defaultValue?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +89,6 @@ export interface SmartUiComponentProps {
|
|||||||
onInputChange: (input: AnyDisplay, newValue: InputType) => void;
|
onInputChange: (input: AnyDisplay, newValue: InputType) => void;
|
||||||
onError: (hasError: boolean) => void;
|
onError: (hasError: boolean) => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
getTranslation: TFunction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SmartUiComponentState {
|
interface SmartUiComponentState {
|
||||||
@@ -124,10 +122,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
private renderInfo(info: Info): JSX.Element {
|
private renderInfo(info: Info): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<MessageBar styles={{ root: { width: 400 } }}>
|
<MessageBar styles={{ root: { width: 400 } }}>
|
||||||
{this.props.getTranslation(info.messageTKey)}
|
{info.message}
|
||||||
{info.link && (
|
{info.link && (
|
||||||
<Link href={info.link.href} target="_blank">
|
<Link href={info.link.href} target="_blank">
|
||||||
{this.props.getTranslation(info.link.textTKey)}
|
{info.link.text}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
@@ -141,10 +139,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
<div className="stringInputContainer">
|
<div className="stringInputContainer">
|
||||||
<TextField
|
<TextField
|
||||||
id={`${input.dataFieldName}-textField-input`}
|
id={`${input.dataFieldName}-textField-input`}
|
||||||
label={this.props.getTranslation(input.labelTKey)}
|
label={input.label}
|
||||||
type="text"
|
type="text"
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
placeholder={this.props.getTranslation(input.placeholderTKey)}
|
placeholder={input.placeholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(_, newValue) => this.props.onInputChange(input, newValue)}
|
onChange={(_, newValue) => this.props.onInputChange(input, newValue)}
|
||||||
styles={{
|
styles={{
|
||||||
@@ -167,10 +165,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
const description = input.description;
|
const description = input.description;
|
||||||
return (
|
return (
|
||||||
<Text id={`${input.dataFieldName}-text-display`}>
|
<Text id={`${input.dataFieldName}-text-display`}>
|
||||||
{this.props.getTranslation(input.description.textTKey)}{" "}
|
{input.description.text}{" "}
|
||||||
{description.link && (
|
{description.link && (
|
||||||
<Link target="_blank" href={input.description.link.href}>
|
<Link target="_blank" href={input.description.link.href}>
|
||||||
{this.props.getTranslation(input.description.link.textTKey)}
|
{input.description.link.text}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -221,12 +219,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
};
|
};
|
||||||
|
|
||||||
private renderNumberInput(input: NumberInput): JSX.Element {
|
private renderNumberInput(input: NumberInput): JSX.Element {
|
||||||
const { labelTKey, min, max, dataFieldName, step } = input;
|
const { label, min, max, dataFieldName, step } = input;
|
||||||
const props = {
|
const props = {
|
||||||
label: this.props.getTranslation(labelTKey),
|
label: label,
|
||||||
min: min,
|
min: min,
|
||||||
max: max,
|
max: max,
|
||||||
ariaLabel: labelTKey,
|
ariaLabel: label,
|
||||||
step: step,
|
step: step,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,10 +284,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
return (
|
return (
|
||||||
<Toggle
|
<Toggle
|
||||||
id={`${input.dataFieldName}-toggle-input`}
|
id={`${input.dataFieldName}-toggle-input`}
|
||||||
label={this.props.getTranslation(input.labelTKey)}
|
label={input.label}
|
||||||
checked={value || false}
|
checked={value || false}
|
||||||
onText={this.props.getTranslation(input.trueLabelTKey)}
|
onText={input.trueLabel}
|
||||||
offText={this.props.getTranslation(input.falseLabelTKey)}
|
offText={input.falseLabel}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(event, checked: boolean) => this.props.onInputChange(input, checked)}
|
onChange={(event, checked: boolean) => this.props.onInputChange(input, checked)}
|
||||||
styles={{ root: { width: 400 } }}
|
styles={{ root: { width: 400 } }}
|
||||||
@@ -298,7 +296,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderChoiceInput(input: ChoiceInput): JSX.Element {
|
private renderChoiceInput(input: ChoiceInput): JSX.Element {
|
||||||
const { labelTKey, defaultKey, dataFieldName, choices, placeholderTKey } = input;
|
const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
|
||||||
const value = this.props.currentValues.get(dataFieldName)?.value as string;
|
const value = this.props.currentValues.get(dataFieldName)?.value as string;
|
||||||
const disabled = this.props.disabled || this.props.currentValues.get(dataFieldName)?.disabled;
|
const disabled = this.props.disabled || this.props.currentValues.get(dataFieldName)?.disabled;
|
||||||
let selectedKey = value ? value : defaultKey;
|
let selectedKey = value ? value : defaultKey;
|
||||||
@@ -308,14 +306,14 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
id={`${input.dataFieldName}-dropdown-input`}
|
id={`${input.dataFieldName}-dropdown-input`}
|
||||||
label={this.props.getTranslation(labelTKey)}
|
label={label}
|
||||||
selectedKey={selectedKey}
|
selectedKey={selectedKey}
|
||||||
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
|
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
|
||||||
placeholder={this.props.getTranslation(placeholderTKey)}
|
placeholder={placeholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
options={choices.map((c) => ({
|
options={choices.map((c) => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
text: this.props.getTranslation(c.label),
|
text: c.label,
|
||||||
}))}
|
}))}
|
||||||
styles={{
|
styles={{
|
||||||
root: { width: 400 },
|
root: { width: 400 },
|
||||||
|
|||||||
@@ -2346,13 +2346,11 @@ export default class Explorer {
|
|||||||
this.tabsManager.activateTab(notebookTab);
|
this.tabsManager.activateTab(notebookTab);
|
||||||
} else {
|
} else {
|
||||||
const options: NotebookTabOptions = {
|
const options: NotebookTabOptions = {
|
||||||
account: userContext.databaseAccount,
|
|
||||||
tabKind: ViewModels.CollectionTabKind.NotebookV2,
|
tabKind: ViewModels.CollectionTabKind.NotebookV2,
|
||||||
node: null,
|
node: null,
|
||||||
title: notebookContentItem.name,
|
title: notebookContentItem.name,
|
||||||
tabPath: notebookContentItem.path,
|
tabPath: notebookContentItem.path,
|
||||||
collection: null,
|
collection: null,
|
||||||
masterKey: userContext.masterKey || "",
|
|
||||||
hashLocation: "notebooks",
|
hashLocation: "notebooks",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
|
|||||||
@@ -36,36 +36,7 @@ export class CommandBarComponentButtonFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newCollectionBtn = CommandBarComponentButtonFactory.createNewCollectionGroup(container);
|
const newCollectionBtn = CommandBarComponentButtonFactory.createNewCollectionGroup(container);
|
||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [newCollectionBtn];
|
||||||
|
|
||||||
if (container.isFeatureEnabled && container.isFeatureEnabled("regionselectbutton")) {
|
|
||||||
const regions = [{ name: "West US" }, { name: "East US" }, { name: "North Europe" }];
|
|
||||||
buttons.push({
|
|
||||||
iconSrc: null,
|
|
||||||
onCommandClick: () => {},
|
|
||||||
commandButtonLabel: null,
|
|
||||||
hasPopup: false,
|
|
||||||
isDropdown: true,
|
|
||||||
dropdownPlaceholder: "West US",
|
|
||||||
dropdownSelectedKey: "West US",
|
|
||||||
dropdownWidth: 100,
|
|
||||||
children: regions.map(
|
|
||||||
(region) =>
|
|
||||||
({
|
|
||||||
iconSrc: null,
|
|
||||||
onCommandClick: () => {},
|
|
||||||
commandButtonLabel: region.name,
|
|
||||||
dropdownItemKey: region.name,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: false,
|
|
||||||
ariaLabel: "",
|
|
||||||
} as CommandButtonComponentProps)
|
|
||||||
),
|
|
||||||
ariaLabel: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.push(newCollectionBtn);
|
|
||||||
|
|
||||||
const addSynapseLink = CommandBarComponentButtonFactory.createOpenSynapseLinkDialogButton(container);
|
const addSynapseLink = CommandBarComponentButtonFactory.createOpenSynapseLinkDialogButton(container);
|
||||||
if (addSynapseLink) {
|
if (addSynapseLink) {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.mongoQueryComponent {
|
||||||
|
margin-left: 10px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:before {
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queryInput {
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import MonacoEditor from "@nteract/monaco-editor";
|
||||||
|
import { PrimaryButton } from "office-ui-fabric-react";
|
||||||
|
import { ChoiceGroup, IChoiceGroupOption } from "office-ui-fabric-react/lib/ChoiceGroup";
|
||||||
|
import Outputs from "@nteract/stateful-components/lib/outputs";
|
||||||
|
import { KernelOutputError, StreamText } from "@nteract/outputs";
|
||||||
|
import TransformMedia from "@nteract/stateful-components/lib/outputs/transform-media";
|
||||||
|
import { actions, selectors, AppState, ContentRef, KernelRef } from "@nteract/core";
|
||||||
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import "./MongoQueryComponent.less";
|
||||||
|
interface MongoQueryComponentPureProps {
|
||||||
|
contentRef: ContentRef;
|
||||||
|
kernelRef: KernelRef;
|
||||||
|
databaseId: string;
|
||||||
|
collectionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoQueryComponentDispatchProps {
|
||||||
|
runCell: (contentRef: ContentRef, cellId: string) => void;
|
||||||
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void;
|
||||||
|
updateCell: (text: string, id: string, contentRef: ContentRef) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputType = "rich" | "json";
|
||||||
|
|
||||||
|
interface MongoQueryComponentState {
|
||||||
|
query: string;
|
||||||
|
outputType: OutputType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: IChoiceGroupOption[] = [
|
||||||
|
{ key: "rich", text: "Rich Output" },
|
||||||
|
{ key: "json", text: "Json Output" },
|
||||||
|
];
|
||||||
|
|
||||||
|
type MongoQueryComponentProps = MongoQueryComponentPureProps & StateProps & MongoQueryComponentDispatchProps;
|
||||||
|
export class MongoQueryComponent extends React.Component<MongoQueryComponentProps, MongoQueryComponentState> {
|
||||||
|
constructor(props: MongoQueryComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
query: this.props.inputValue,
|
||||||
|
outputType: "rich",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
loadTransform(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onExecute = () => {
|
||||||
|
const query = JSON.parse(this.state.query);
|
||||||
|
query["database"] = this.props.databaseId;
|
||||||
|
query["collection"] = this.props.collectionId;
|
||||||
|
query["outputType"] = this.state.outputType;
|
||||||
|
|
||||||
|
this.props.updateCell(JSON.stringify(query), this.props.firstCellId, this.props.contentRef);
|
||||||
|
this.props.runCell(this.props.contentRef, this.props.firstCellId);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onOutputTypeChange = (
|
||||||
|
e: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option: IChoiceGroupOption
|
||||||
|
): void => {
|
||||||
|
const outputType = option.key as OutputType;
|
||||||
|
this.setState({ outputType }, () => this.onInputChange(this.props.inputValue));
|
||||||
|
};
|
||||||
|
|
||||||
|
private onInputChange = (text: string) => {
|
||||||
|
this.setState({ query: text });
|
||||||
|
};
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
const { firstCellId: id, contentRef } = this.props;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mongoQueryComponent">
|
||||||
|
<div className="queryInput">
|
||||||
|
<MonacoEditor
|
||||||
|
id={this.props.firstCellId}
|
||||||
|
contentRef={this.props.contentRef}
|
||||||
|
theme={""}
|
||||||
|
language="json"
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
value={this.state.query}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<PrimaryButton text="Run" onClick={this.onExecute} disabled={!this.props.firstCellId} />
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.outputType}
|
||||||
|
options={options}
|
||||||
|
onChange={this.onOutputTypeChange}
|
||||||
|
label="Output Type"
|
||||||
|
styles={{ root: { marginTop: 0 } }}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
<Outputs id={id} contentRef={contentRef}>
|
||||||
|
<TransformMedia output_type={"display_data"} id={id} contentRef={contentRef} />
|
||||||
|
<TransformMedia output_type={"execute_result"} id={id} contentRef={contentRef} />
|
||||||
|
<KernelOutputError />
|
||||||
|
<StreamText />
|
||||||
|
</Outputs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
firstCellId: string;
|
||||||
|
inputValue: string;
|
||||||
|
}
|
||||||
|
interface InitialProps {
|
||||||
|
contentRef: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redux
|
||||||
|
const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => {
|
||||||
|
const { contentRef } = initialProps;
|
||||||
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
let firstCellId;
|
||||||
|
let inputValue = "";
|
||||||
|
const content = selectors.content(state, { contentRef });
|
||||||
|
if (content?.type === "notebook") {
|
||||||
|
const cellOrder = selectors.notebook.cellOrder(content.model);
|
||||||
|
if (cellOrder.size > 0) {
|
||||||
|
firstCellId = cellOrder.first() as string;
|
||||||
|
const cell = selectors.notebook.cellById(content.model, { id: firstCellId });
|
||||||
|
|
||||||
|
// Parse to extract filter and output type
|
||||||
|
const cellValue = cell.get("source", "");
|
||||||
|
if (cellValue) {
|
||||||
|
try {
|
||||||
|
const filterValue = JSON.parse(cellValue).filter;
|
||||||
|
if (filterValue) {
|
||||||
|
inputValue = filterValue;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Could not parse", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstCellId,
|
||||||
|
inputValue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: MongoQueryComponentProps) => {
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
|
return {
|
||||||
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => {
|
||||||
|
return dispatch(
|
||||||
|
actions.addTransform({
|
||||||
|
mediaType: transform.MIMETYPE,
|
||||||
|
component: transform,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
runCell: (contentRef: ContentRef, cellId: string) => {
|
||||||
|
return dispatch(
|
||||||
|
actions.executeCell({
|
||||||
|
contentRef,
|
||||||
|
id: cellId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateCell: (text: string, id: string, contentRef: ContentRef) => {
|
||||||
|
dispatch(actions.updateCellSource({ id, contentRef, value: text }));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return mapDispatchToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(makeMapStateToProps, makeMapDispatchToProps)(MongoQueryComponent);
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
|
import {
|
||||||
|
NotebookComponentBootstrapper,
|
||||||
|
NotebookComponentBootstrapperOptions,
|
||||||
|
} from "../NotebookComponent/NotebookComponentBootstrapper";
|
||||||
|
import MongoQueryComponent from "../MongoQueryComponent/MongoQueryComponent";
|
||||||
|
import { actions, createContentRef, createKernelRef, IContent, KernelRef } from "@nteract/core";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { Notebook } from "@nteract/commutable";
|
||||||
|
|
||||||
|
export class MongoQueryComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
||||||
|
public parameters: unknown;
|
||||||
|
private kernelRef: KernelRef;
|
||||||
|
|
||||||
|
constructor(options: NotebookComponentBootstrapperOptions, private databaseId: string, private collectionId: string) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
if (!this.contentRef) {
|
||||||
|
this.contentRef = createContentRef();
|
||||||
|
this.kernelRef = createKernelRef();
|
||||||
|
|
||||||
|
const notebook: Notebook = {
|
||||||
|
cells: [
|
||||||
|
{
|
||||||
|
cell_type: "code",
|
||||||
|
metadata: {},
|
||||||
|
execution_count: 0,
|
||||||
|
outputs: [],
|
||||||
|
source: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
displayName: "Mongo",
|
||||||
|
language: "mongocli",
|
||||||
|
name: "mongo",
|
||||||
|
},
|
||||||
|
language_info: {
|
||||||
|
file_extension: "ipynb",
|
||||||
|
mimetype: "application/json",
|
||||||
|
name: "mongo",
|
||||||
|
version: "1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nbformat: 4,
|
||||||
|
nbformat_minor: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const model: IContent<"notebook"> = {
|
||||||
|
name: "mongo-query-component-notebook.ipynb",
|
||||||
|
path: "mongo-query-component-notebook.ipynb",
|
||||||
|
type: "notebook",
|
||||||
|
writable: true,
|
||||||
|
created: "",
|
||||||
|
last_modified: "",
|
||||||
|
mimetype: "application/x-ipynb+json",
|
||||||
|
content: notebook,
|
||||||
|
format: "json",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request fetching notebook content
|
||||||
|
this.getStore().dispatch(
|
||||||
|
actions.fetchContentFulfilled({
|
||||||
|
filepath: model.path,
|
||||||
|
model,
|
||||||
|
kernelRef: this.kernelRef,
|
||||||
|
contentRef: this.contentRef,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderComponent(): JSX.Element {
|
||||||
|
const props = {
|
||||||
|
contentRef: this.contentRef,
|
||||||
|
kernelRef: this.kernelRef,
|
||||||
|
databaseId: this.databaseId,
|
||||||
|
collectionId: this.collectionId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider store={this.getStore()}>
|
||||||
|
<MongoQueryComponent {...props} />;
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,16 +41,16 @@ export class NotebookContainerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||||
if (this.isResettingWorkspace) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
||||||
const error = "No server endpoint detected";
|
const error = "No server endpoint detected";
|
||||||
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isResettingWorkspace) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
||||||
|
|||||||
@@ -818,7 +818,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
let indexingPolicy: DataModels.IndexingPolicy;
|
let indexingPolicy: DataModels.IndexingPolicy;
|
||||||
let createMongoWildcardIndex: boolean;
|
let createMongoWildcardIndex: boolean;
|
||||||
// todo - remove mongo indexing policy ticket # 616274
|
// todo - remove mongo indexing policy ticket # 616274
|
||||||
if (this.container.isPreferredApiMongoDB() && this.container.isEnableMongoCapabilityPresent()) {
|
if (this.container.isPreferredApiMongoDB()) {
|
||||||
createMongoWildcardIndex = this.shouldCreateMongoWildcardIndex();
|
createMongoWildcardIndex = this.shouldCreateMongoWildcardIndex();
|
||||||
} else if (this.showIndexingOptionsForSharedThroughput()) {
|
} else if (this.showIndexingOptionsForSharedThroughput()) {
|
||||||
if (this.useIndexingForSharedThroughput()) {
|
if (this.useIndexingForSharedThroughput()) {
|
||||||
|
|||||||
1
src/Explorer/Tabs/MongoDocumentsTabV2.html
Normal file
1
src/Explorer/Tabs/MongoDocumentsTabV2.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<div data-bind="react:mongoQueryComponentAdapter" style="height: 100%"></div>
|
||||||
49
src/Explorer/Tabs/MongoDocumentsTabV2.ts
Normal file
49
src/Explorer/Tabs/MongoDocumentsTabV2.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import * as Q from "q";
|
||||||
|
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||||
|
import { MongoQueryComponentAdapter } from "../Notebook/MongoQueryComponent/MongoQueryComponentAdapter";
|
||||||
|
|
||||||
|
export default class MongoDocumentsTabV2 extends NotebookTabBase {
|
||||||
|
private mongoQueryComponentAdapter: MongoQueryComponentAdapter;
|
||||||
|
|
||||||
|
constructor(options: NotebookTabBaseOptions) {
|
||||||
|
super(options);
|
||||||
|
this.mongoQueryComponentAdapter = new MongoQueryComponentAdapter(
|
||||||
|
{
|
||||||
|
contentRef: undefined,
|
||||||
|
notebookClient: NotebookTabBase.clientManager,
|
||||||
|
},
|
||||||
|
options.collection?.databaseId,
|
||||||
|
options.collection?.id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCloseTabButtonClick(): Q.Promise<void> {
|
||||||
|
super.onCloseTabButtonClick();
|
||||||
|
|
||||||
|
// const cleanup = () => {
|
||||||
|
// this.notebookComponentAdapter.notebookShutdown();
|
||||||
|
// this.isActive(false);
|
||||||
|
// super.onCloseTabButtonClick();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// if (this.notebookComponentAdapter.isContentDirty()) {
|
||||||
|
// this.container.showOkCancelModalDialog(
|
||||||
|
// "Close without saving?",
|
||||||
|
// `File has unsaved changes, close without saving?`,
|
||||||
|
// "Close",
|
||||||
|
// cleanup,
|
||||||
|
// "Cancel",
|
||||||
|
// undefined
|
||||||
|
// );
|
||||||
|
// return Q.resolve(null);
|
||||||
|
// } else {
|
||||||
|
// cleanup();
|
||||||
|
// return Q.resolve(null);
|
||||||
|
// }
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected buildCommandBarOptions(): void {
|
||||||
|
this.updateNavbarWithTabsButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/Explorer/Tabs/NotebookTabBase.ts
Normal file
50
src/Explorer/Tabs/NotebookTabBase.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
|
import { NotebookClientV2 } from "../Notebook/NotebookClientV2";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import { ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { Areas } from "../../Common/Constants";
|
||||||
|
|
||||||
|
export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
|
||||||
|
container: Explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every notebook-based tab inherits from this class. It holds the static reference to a notebook client (singleton)
|
||||||
|
*/
|
||||||
|
export default class NotebookTabBase extends TabsBase {
|
||||||
|
protected static clientManager: NotebookClientV2;
|
||||||
|
protected container: Explorer;
|
||||||
|
|
||||||
|
constructor(options: NotebookTabBaseOptions) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.container = options.container;
|
||||||
|
|
||||||
|
if (!NotebookTabBase.clientManager) {
|
||||||
|
NotebookTabBase.clientManager = new NotebookClientV2({
|
||||||
|
connectionInfo: this.container.notebookServerInfo(),
|
||||||
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
contentProvider: this.container.notebookManager?.notebookContentProvider,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override base behavior
|
||||||
|
*/
|
||||||
|
protected getContainer(): Explorer {
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected traceTelemetry(actionType: number): void {
|
||||||
|
TelemetryProcessor.trace(actionType, ActionModifiers.Mark, {
|
||||||
|
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
|
||||||
|
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Areas.Notebook,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,7 @@ import * as _ from "underscore";
|
|||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import TabsBase from "./TabsBase";
|
|
||||||
|
|
||||||
import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg";
|
import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg";
|
||||||
import CutIcon from "../../../images/notebook/Notebook-cut.svg";
|
import CutIcon from "../../../images/notebook/Notebook-cut.svg";
|
||||||
@@ -17,33 +15,27 @@ import SaveIcon from "../../../images/save-cosmos.svg";
|
|||||||
import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg";
|
import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg";
|
||||||
import InterruptKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
import InterruptKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
||||||
import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { ArmApiVersions } from "../../Common/Constants";
|
||||||
import { Areas, ArmApiVersions } from "../../Common/Constants";
|
|
||||||
import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
||||||
import { NotebookConfigurationUtils } from "../../Utils/NotebookConfigurationUtils";
|
import { NotebookConfigurationUtils } from "../../Utils/NotebookConfigurationUtils";
|
||||||
import { KernelSpecsDisplay, NotebookClientV2 } from "../Notebook/NotebookClientV2";
|
import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { toJS, stringifyNotebook } from "@nteract/commutable";
|
import { toJS, stringifyNotebook } from "@nteract/commutable";
|
||||||
import { appInsights } from "../../Shared/appInsights";
|
import { appInsights } from "../../Shared/appInsights";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||||
|
|
||||||
export interface NotebookTabOptions extends ViewModels.TabOptions {
|
export interface NotebookTabOptions extends NotebookTabBaseOptions {
|
||||||
account: DataModels.DatabaseAccount;
|
|
||||||
masterKey: string;
|
|
||||||
container: Explorer;
|
|
||||||
notebookContentItem: NotebookContentItem;
|
notebookContentItem: NotebookContentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NotebookTabV2 extends TabsBase {
|
export default class NotebookTabV2 extends NotebookTabBase {
|
||||||
private static clientManager: NotebookClientV2;
|
|
||||||
private container: Explorer;
|
|
||||||
public notebookPath: ko.Observable<string>;
|
public notebookPath: ko.Observable<string>;
|
||||||
private selectedSparkPool: ko.Observable<string>;
|
private selectedSparkPool: ko.Observable<string>;
|
||||||
private notebookComponentAdapter: NotebookComponentAdapter;
|
private notebookComponentAdapter: NotebookComponentAdapter;
|
||||||
@@ -52,16 +44,6 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
|
|
||||||
if (!NotebookTabV2.clientManager) {
|
|
||||||
NotebookTabV2.clientManager = new NotebookClientV2({
|
|
||||||
connectionInfo: this.container.notebookServerInfo(),
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
contentProvider: this.container.notebookManager?.notebookContentProvider,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
||||||
|
|
||||||
this.container.notebookServerInfo.subscribe((newValue: DataModels.NotebookWorkspaceConnectionInfo) => {
|
this.container.notebookServerInfo.subscribe((newValue: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||||
@@ -71,7 +53,7 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
||||||
contentItem: options.notebookContentItem,
|
contentItem: options.notebookContentItem,
|
||||||
notebooksBasePath: this.container.getNotebookBasePath(),
|
notebooksBasePath: this.container.getNotebookBasePath(),
|
||||||
notebookClient: NotebookTabV2.clientManager,
|
notebookClient: NotebookTabBase.clientManager,
|
||||||
onUpdateKernelInfo: this.onKernelUpdate,
|
onUpdateKernelInfo: this.onKernelUpdate,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,10 +99,6 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
return await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName());
|
return await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer(): Explorer {
|
|
||||||
return this.container;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||||
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
|
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
|
||||||
|
|
||||||
@@ -504,12 +482,4 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
|
|
||||||
this.container.copyNotebook(notebookContent.name, content);
|
this.container.copyNotebook(notebookContent.name, content);
|
||||||
};
|
};
|
||||||
|
|
||||||
private traceTelemetry(actionType: number) {
|
|
||||||
TelemetryProcessor.trace(actionType, ActionModifiers.Mark, {
|
|
||||||
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
|
|
||||||
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import SparkMasterTabTemplate from "./SparkMasterTab.html";
|
|||||||
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
|
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
|
||||||
import TerminalTabTemplate from "./TerminalTab.html";
|
import TerminalTabTemplate from "./TerminalTab.html";
|
||||||
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
|
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
|
||||||
|
import MongoDocumentsTabV2Template from "./MongoDocumentsTabV2.html";
|
||||||
import MongoQueryTabTemplate from "./MongoQueryTab.html";
|
import MongoQueryTabTemplate from "./MongoQueryTab.html";
|
||||||
import MongoShellTabTemplate from "./MongoShellTab.html";
|
import MongoShellTabTemplate from "./MongoShellTab.html";
|
||||||
import QueryTabTemplate from "./QueryTab.html";
|
import QueryTabTemplate from "./QueryTab.html";
|
||||||
@@ -105,6 +106,15 @@ export class MongoQueryTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MongoDocumentsTabV2 {
|
||||||
|
constructor() {
|
||||||
|
return {
|
||||||
|
viewModel: TabComponent,
|
||||||
|
template: MongoDocumentsTabV2Template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class MongoShellTab {
|
export class MongoShellTab {
|
||||||
constructor() {
|
constructor() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
|
import Q from "q";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import UploadWorker from "worker-loader!../../workers/upload";
|
import UploadWorker from "worker-loader!../../workers/upload";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
@@ -22,6 +23,7 @@ import ConflictsTab from "../Tabs/ConflictsTab";
|
|||||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||||
import GraphTab from "../Tabs/GraphTab";
|
import GraphTab from "../Tabs/GraphTab";
|
||||||
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
|
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
|
||||||
|
import MongoDocumentsTabV2 from "../Tabs/MongoDocumentsTabV2";
|
||||||
import MongoQueryTab from "../Tabs/MongoQueryTab";
|
import MongoQueryTab from "../Tabs/MongoQueryTab";
|
||||||
import MongoShellTab from "../Tabs/MongoShellTab";
|
import MongoShellTab from "../Tabs/MongoShellTab";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
@@ -253,9 +255,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandCollection(): void {
|
public expandCollection(): Q.Promise<any> {
|
||||||
if (this.isCollectionExpanded()) {
|
if (this.isCollectionExpanded()) {
|
||||||
return;
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isCollectionExpanded(true);
|
this.isCollectionExpanded(true);
|
||||||
@@ -267,6 +269,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Q.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDocumentDBDocumentsClick() {
|
public onDocumentDBDocumentsClick() {
|
||||||
@@ -493,11 +497,11 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
const mongoDocumentsTabs: MongoDocumentsTabV2[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as MongoDocumentsTab[];
|
) as MongoDocumentsTabV2[];
|
||||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
let mongoDocumentsTab: MongoDocumentsTabV2 = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||||
|
|
||||||
if (mongoDocumentsTab) {
|
if (mongoDocumentsTab) {
|
||||||
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
||||||
@@ -512,9 +516,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
|
|
||||||
mongoDocumentsTab = new MongoDocumentsTab({
|
mongoDocumentsTab = new MongoDocumentsTabV2({
|
||||||
partitionKey: this.partitionKey,
|
container: this.container,
|
||||||
documentIds: this.documentIds,
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "Documents",
|
title: "Documents",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
@@ -544,7 +547,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, (tab) => {
|
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, (tab) => {
|
||||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
});
|
});
|
||||||
@@ -577,7 +580,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
settingsTabV2: SettingsTabV2,
|
settingsTabV2: SettingsTabV2,
|
||||||
traceStartData: any,
|
traceStartData: any,
|
||||||
settingsTabOptions: ViewModels.TabOptions,
|
settingsTabOptions: ViewModels.TabOptions,
|
||||||
getPendingNotification: Promise<DataModels.Notification>
|
getPendingNotification: Q.Promise<DataModels.Notification>
|
||||||
): void => {
|
): void => {
|
||||||
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
|
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
|
||||||
...settingsTabOptions,
|
...settingsTabOptions,
|
||||||
@@ -977,19 +980,19 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.container.deleteCollectionConfirmationPane.open();
|
this.container.deleteCollectionConfirmationPane.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadFiles = (fileList: FileList): Promise<UploadDetails> => {
|
public uploadFiles = (fileList: FileList): Q.Promise<UploadDetails> => {
|
||||||
// TODO: right now web worker is not working with AAD flow. Use main thread for upload for now until we have backend upload capability
|
// TODO: right now web worker is not working with AAD flow. Use main thread for upload for now until we have backend upload capability
|
||||||
if (configContext.platform === Platform.Hosted && window.authType === AuthType.AAD) {
|
if (configContext.platform === Platform.Hosted && window.authType === AuthType.AAD) {
|
||||||
return this._uploadFilesCors(fileList);
|
return this._uploadFilesCors(fileList);
|
||||||
}
|
}
|
||||||
const documentUploader: Worker = new UploadWorker();
|
const documentUploader: Worker = new UploadWorker();
|
||||||
|
const deferred: Q.Deferred<UploadDetails> = Q.defer<UploadDetails>();
|
||||||
let inProgressNotificationId: string = "";
|
let inProgressNotificationId: string = "";
|
||||||
|
|
||||||
if (!fileList || fileList.length === 0) {
|
if (!fileList || fileList.length === 0) {
|
||||||
return Promise.reject("No files specified");
|
return Q.reject("No files specified");
|
||||||
}
|
}
|
||||||
|
documentUploader.onmessage = (event: MessageEvent) => {
|
||||||
const onmessage = (resolve: (value: UploadDetails) => void, reject: (reason: any) => void, event: MessageEvent) => {
|
|
||||||
const numSuccessful: number = event.data.numUploadsSuccessful;
|
const numSuccessful: number = event.data.numUploadsSuccessful;
|
||||||
const numFailed: number = event.data.numUploadsFailed;
|
const numFailed: number = event.data.numUploadsFailed;
|
||||||
const runtimeError: string = event.data.runtimeError;
|
const runtimeError: string = event.data.runtimeError;
|
||||||
@@ -998,26 +1001,31 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressNotificationId);
|
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressNotificationId);
|
||||||
documentUploader.terminate();
|
documentUploader.terminate();
|
||||||
if (!!runtimeError) {
|
if (!!runtimeError) {
|
||||||
reject(runtimeError);
|
deferred.reject(runtimeError);
|
||||||
} else if (numSuccessful === 0) {
|
} else if (numSuccessful === 0) {
|
||||||
// all uploads failed
|
// all uploads failed
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to upload all documents to container ${this.id()}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to upload all documents to container ${this.id()}`
|
||||||
|
);
|
||||||
} else if (numFailed > 0) {
|
} else if (numFailed > 0) {
|
||||||
NotificationConsoleUtils.logConsoleError(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
`Failed to upload ${numFailed} of ${numSuccessful + numFailed} documents to container ${this.id()}`
|
`Failed to upload ${numFailed} of ${numSuccessful + numFailed} documents to container ${this.id()}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
NotificationConsoleUtils.logConsoleInfo(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
`Successfully uploaded all ${numSuccessful} documents to container ${this.id()}`
|
`Successfully uploaded all ${numSuccessful} documents to container ${this.id()}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._logUploadDetailsInConsole(uploadDetails);
|
this._logUploadDetailsInConsole(uploadDetails);
|
||||||
resolve(uploadDetails);
|
deferred.resolve(uploadDetails);
|
||||||
};
|
};
|
||||||
function onerror(reject: (reason: any) => void, event: ErrorEvent) {
|
documentUploader.onerror = (event: ErrorEvent): void => {
|
||||||
documentUploader.terminate();
|
documentUploader.terminate();
|
||||||
reject(event.error);
|
deferred.reject(event.error);
|
||||||
}
|
};
|
||||||
|
|
||||||
const uploaderMessage: StartUploadMessageParams = {
|
const uploaderMessage: StartUploadMessageParams = {
|
||||||
files: fileList,
|
files: fileList,
|
||||||
@@ -1032,33 +1040,42 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise<UploadDetails>((resolve, reject) => {
|
documentUploader.postMessage(uploaderMessage);
|
||||||
documentUploader.onmessage = onmessage.bind(null, resolve, reject);
|
inProgressNotificationId = NotificationConsoleUtils.logConsoleMessage(
|
||||||
documentUploader.onerror = onerror.bind(null, reject);
|
ConsoleDataType.InProgress,
|
||||||
|
`Uploading and creating documents in container ${this.id()}`
|
||||||
|
);
|
||||||
|
|
||||||
documentUploader.postMessage(uploaderMessage);
|
return deferred.promise;
|
||||||
inProgressNotificationId = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Uploading and creating documents in container ${this.id()}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private async _uploadFilesCors(files: FileList): Promise<UploadDetails> {
|
private _uploadFilesCors(files: FileList): Q.Promise<UploadDetails> {
|
||||||
const data = await Promise.all(Array.from(files).map((file) => this._uploadFile(file)));
|
const deferred: Q.Deferred<UploadDetails> = Q.defer<UploadDetails>();
|
||||||
|
const promises: Array<Q.Promise<UploadDetailsRecord>> = [];
|
||||||
|
|
||||||
return { data };
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
promises.push(this._uploadFile(files[i]));
|
||||||
|
}
|
||||||
|
Q.all(promises).then((uploadDetails: Array<UploadDetailsRecord>) => {
|
||||||
|
deferred.resolve({ data: uploadDetails });
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _uploadFile(file: File): Promise<UploadDetailsRecord> {
|
private _uploadFile(file: File): Q.Promise<UploadDetailsRecord> {
|
||||||
|
const deferred: Q.Deferred<UploadDetailsRecord> = Q.defer();
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
const onload = (resolve: (value: UploadDetailsRecord) => void, evt: any): void => {
|
reader.onload = (evt: any): void => {
|
||||||
const fileData: string = evt.target.result;
|
const fileData: string = evt.target.result;
|
||||||
this._createDocumentsFromFile(file.name, fileData).then((record) => resolve(record));
|
this._createDocumentsFromFile(file.name, fileData).then((record) => {
|
||||||
|
deferred.resolve(record);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onerror = (resolve: (value: UploadDetailsRecord) => void, evt: ProgressEvent): void => {
|
reader.onerror = (evt: ProgressEvent): void => {
|
||||||
resolve({
|
deferred.resolve({
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
numSucceeded: 0,
|
numSucceeded: 0,
|
||||||
numFailed: 1,
|
numFailed: 1,
|
||||||
@@ -1066,11 +1083,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise<UploadDetailsRecord>((resolve) => {
|
reader.readAsText(file);
|
||||||
reader.onload = onload.bind(this, resolve);
|
|
||||||
reader.onerror = onerror.bind(this, resolve);
|
return deferred.promise;
|
||||||
reader.readAsText(file);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createDocumentsFromFile(fileName: string, documentContent: string): Promise<UploadDetailsRecord> {
|
private async _createDocumentsFromFile(fileName: string, documentContent: string): Promise<UploadDetailsRecord> {
|
||||||
@@ -1104,35 +1119,46 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getPendingThroughputSplitNotification(): Promise<DataModels.Notification> {
|
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
||||||
if (!this.container) {
|
if (!this.container) {
|
||||||
return undefined;
|
return Q.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const throughputUpdateRegExp = new RegExp("Throughput update (.*) in progress");
|
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
||||||
try {
|
fetchPortalNotifications().then(
|
||||||
const notifications = await fetchPortalNotifications();
|
(notifications: DataModels.Notification[]) => {
|
||||||
if (!notifications) {
|
if (!notifications || notifications.length === 0) {
|
||||||
return undefined;
|
deferred.resolve(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
|
||||||
|
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
||||||
|
return (
|
||||||
|
notification.kind === "message" &&
|
||||||
|
notification.collectionName === this.id() &&
|
||||||
|
notification.description &&
|
||||||
|
throughputUpdateRegExp.test(notification.description)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
deferred.resolve(pendingNotification);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
Logger.logError(
|
||||||
|
JSON.stringify({
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
accountName: this.container && this.container.databaseAccount(),
|
||||||
|
databaseName: this.databaseId,
|
||||||
|
collectionName: this.id(),
|
||||||
|
}),
|
||||||
|
"Settings tree node"
|
||||||
|
);
|
||||||
|
deferred.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return notifications.find(
|
return deferred.promise;
|
||||||
({ kind, collectionName, description = "" }) =>
|
|
||||||
kind === "message" && collectionName === this.id() && throughputUpdateRegExp.test(description)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
accountName: this.container && this.container.databaseAccount(),
|
|
||||||
databaseName: this.databaseId,
|
|
||||||
collectionName: this.id(),
|
|
||||||
}),
|
|
||||||
"Settings tree node"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
this.isCollectionExpanded = ko.observable<boolean>(true);
|
this.isCollectionExpanded = ko.observable<boolean>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandCollection(): void {
|
public expandCollection(): Q.Promise<void> {
|
||||||
if (this.isCollectionExpanded()) {
|
if (this.isCollectionExpanded()) {
|
||||||
return;
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isCollectionExpanded(true);
|
this.isCollectionExpanded(true);
|
||||||
@@ -55,6 +55,8 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Q.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public collapseCollection() {
|
public collapseCollection() {
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"translations": {
|
|
||||||
"Common": {
|
|
||||||
"Save": "Save",
|
|
||||||
"Discard": "Discard",
|
|
||||||
"Refresh": "Refesh"
|
|
||||||
},
|
|
||||||
"SelfServeExample": {
|
|
||||||
"North Central US": "North Central US",
|
|
||||||
"West US": "West US",
|
|
||||||
"East US 2": "East US 2",
|
|
||||||
"ClassInfo": "This is a self serve class",
|
|
||||||
"RegionDropdownInfo": "More regions can be added in the future.",
|
|
||||||
"ValidationError": "Regions and AccountName should not be empty.",
|
|
||||||
"DescriptionText": "This class sets collection and database throughput.",
|
|
||||||
"DecriptionLinkText": "Click here for more information",
|
|
||||||
"Regions": "Regions",
|
|
||||||
"RegionsPlaceholder": "Select a region",
|
|
||||||
"Enable Logging": "Enable Logging",
|
|
||||||
"Enable": "Enable",
|
|
||||||
"Disable": "Disable",
|
|
||||||
"Account Name": "Account Name",
|
|
||||||
"AccountNamePlaceHolder": "Enter the account name",
|
|
||||||
"Collection Throughput": "Collection Throughput",
|
|
||||||
"Enable DB level throughput": "Enable DB level throughput",
|
|
||||||
"Database Throughput": "Database Throughput",
|
|
||||||
"RefreshMessage": "Self Serve Example successfully refreshing",
|
|
||||||
"SubmissionMessage": "Submitted successfully"
|
|
||||||
},
|
|
||||||
"SqlX": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -288,9 +288,11 @@ export class TabRouteHandler {
|
|||||||
private _openSprocTabForResource(databaseId: string, collectionId: string, sprocId: string): void {
|
private _openSprocTabForResource(databaseId: string, collectionId: string, sprocId: string): void {
|
||||||
this._executeActionHelper(() => {
|
this._executeActionHelper(() => {
|
||||||
const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId);
|
const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId);
|
||||||
collection && collection.expandCollection();
|
collection &&
|
||||||
const storedProcedure = collection && collection.findStoredProcedureWithId(sprocId);
|
collection.expandCollection().then(() => {
|
||||||
storedProcedure && storedProcedure.open();
|
const storedProcedure = collection && collection.findStoredProcedureWithId(sprocId);
|
||||||
|
storedProcedure && storedProcedure.open();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,9 +319,11 @@ export class TabRouteHandler {
|
|||||||
private _openTriggerTabForResource(databaseId: string, collectionId: string, triggerId: string): void {
|
private _openTriggerTabForResource(databaseId: string, collectionId: string, triggerId: string): void {
|
||||||
this._executeActionHelper(() => {
|
this._executeActionHelper(() => {
|
||||||
const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId);
|
const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId);
|
||||||
collection && collection.expandCollection();
|
collection &&
|
||||||
const trigger = collection && collection.findTriggerWithId(triggerId);
|
collection.expandCollection().then(() => {
|
||||||
trigger && trigger.open();
|
const trigger = collection && collection.findTriggerWithId(triggerId);
|
||||||
|
trigger && trigger.open();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,9 +350,11 @@ export class TabRouteHandler {
|
|||||||
private _openUserDefinedFunctionTabForResource(databaseId: string, collectionId: string, udfId: string): void {
|
private _openUserDefinedFunctionTabForResource(databaseId: string, collectionId: string, udfId: string): void {
|
||||||
this._executeActionHelper(() => {
|
this._executeActionHelper(() => {
|
||||||
const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId);
|
const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId);
|
||||||
collection && collection.expandCollection();
|
collection &&
|
||||||
const userDefinedFunction = collection && collection.findUserDefinedFunctionWithId(udfId);
|
collection.expandCollection().then(() => {
|
||||||
userDefinedFunction && userDefinedFunction.open();
|
const userDefinedFunction = collection && collection.findUserDefinedFunctionWithId(udfId);
|
||||||
|
userDefinedFunction && userDefinedFunction.open();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface Decorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InputOptionsBase {
|
interface InputOptionsBase {
|
||||||
labelTKey: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NumberInputOptions extends InputOptionsBase {
|
export interface NumberInputOptions extends InputOptionsBase {
|
||||||
@@ -19,17 +19,17 @@ export interface NumberInputOptions extends InputOptionsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StringInputOptions extends InputOptionsBase {
|
export interface StringInputOptions extends InputOptionsBase {
|
||||||
placeholderTKey?: (() => Promise<string>) | string;
|
placeholder?: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BooleanInputOptions extends InputOptionsBase {
|
export interface BooleanInputOptions extends InputOptionsBase {
|
||||||
trueLabelTKey: (() => Promise<string>) | string;
|
trueLabel: (() => Promise<string>) | string;
|
||||||
falseLabelTKey: (() => Promise<string>) | string;
|
falseLabel: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChoiceInputOptions extends InputOptionsBase {
|
export interface ChoiceInputOptions extends InputOptionsBase {
|
||||||
choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
||||||
placeholderTKey?: (() => Promise<string>) | string;
|
placeholder?: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DescriptionDisplayOptions {
|
export interface DescriptionDisplayOptions {
|
||||||
@@ -48,7 +48,7 @@ const isNumberInputOptions = (inputOptions: InputOptions): inputOptions is Numbe
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isBooleanInputOptions = (inputOptions: InputOptions): inputOptions is BooleanInputOptions => {
|
const isBooleanInputOptions = (inputOptions: InputOptions): inputOptions is BooleanInputOptions => {
|
||||||
return "trueLabelTKey" in inputOptions;
|
return "trueLabel" in inputOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isChoiceInputOptions = (inputOptions: InputOptions): inputOptions is ChoiceInputOptions => {
|
const isChoiceInputOptions = (inputOptions: InputOptions): inputOptions is ChoiceInputOptions => {
|
||||||
@@ -92,7 +92,7 @@ export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecora
|
|||||||
export const Values = (inputOptions: InputOptions): PropertyDecorator => {
|
export const Values = (inputOptions: InputOptions): PropertyDecorator => {
|
||||||
if (isNumberInputOptions(inputOptions)) {
|
if (isNumberInputOptions(inputOptions)) {
|
||||||
return addToMap(
|
return addToMap(
|
||||||
{ name: "labelTKey", value: inputOptions.labelTKey },
|
{ name: "label", value: inputOptions.label },
|
||||||
{ name: "min", value: inputOptions.min },
|
{ name: "min", value: inputOptions.min },
|
||||||
{ name: "max", value: inputOptions.max },
|
{ name: "max", value: inputOptions.max },
|
||||||
{ name: "step", value: inputOptions.step },
|
{ name: "step", value: inputOptions.step },
|
||||||
@@ -100,22 +100,22 @@ export const Values = (inputOptions: InputOptions): PropertyDecorator => {
|
|||||||
);
|
);
|
||||||
} else if (isBooleanInputOptions(inputOptions)) {
|
} else if (isBooleanInputOptions(inputOptions)) {
|
||||||
return addToMap(
|
return addToMap(
|
||||||
{ name: "labelTKey", value: inputOptions.labelTKey },
|
{ name: "label", value: inputOptions.label },
|
||||||
{ name: "trueLabelTKey", value: inputOptions.trueLabelTKey },
|
{ name: "trueLabel", value: inputOptions.trueLabel },
|
||||||
{ name: "falseLabelTKey", value: inputOptions.falseLabelTKey }
|
{ name: "falseLabel", value: inputOptions.falseLabel }
|
||||||
);
|
);
|
||||||
} else if (isChoiceInputOptions(inputOptions)) {
|
} else if (isChoiceInputOptions(inputOptions)) {
|
||||||
return addToMap(
|
return addToMap(
|
||||||
{ name: "labelTKey", value: inputOptions.labelTKey },
|
{ name: "label", value: inputOptions.label },
|
||||||
{ name: "placeholderTKey", value: inputOptions.placeholderTKey },
|
{ name: "placeholder", value: inputOptions.placeholder },
|
||||||
{ name: "choices", value: inputOptions.choices }
|
{ name: "choices", value: inputOptions.choices }
|
||||||
);
|
);
|
||||||
} else if (isDescriptionDisplayOptions(inputOptions)) {
|
} else if (isDescriptionDisplayOptions(inputOptions)) {
|
||||||
return addToMap({ name: "description", value: inputOptions.description });
|
return addToMap({ name: "description", value: inputOptions.description });
|
||||||
} else {
|
} else {
|
||||||
return addToMap(
|
return addToMap(
|
||||||
{ name: "labelTKey", value: inputOptions.labelTKey },
|
{ name: "label", value: inputOptions.label },
|
||||||
{ name: "placeholderTKey", value: inputOptions.placeholderTKey }
|
{ name: "placeholder", value: inputOptions.placeholder }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,22 +16,10 @@ export interface InitializeResponse {
|
|||||||
dbThroughput: number;
|
dbThroughput: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMaxCollectionThroughput = async (): Promise<number> => {
|
export const getMaxThroughput = async (): Promise<number> => {
|
||||||
return 10000;
|
return 10000;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMinCollectionThroughput = async (): Promise<number> => {
|
|
||||||
return 400;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMaxDatabaseThroughput = async (): Promise<number> => {
|
|
||||||
return 10000;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMinDatabaseThroughput = async (): Promise<number> => {
|
|
||||||
return 400;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const update = async (
|
export const update = async (
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
enableLogging: boolean,
|
enableLogging: boolean,
|
||||||
@@ -71,6 +59,6 @@ export const onRefreshSelfServeExample = async (): Promise<RefreshResult> => {
|
|||||||
const isUpdateInProgress = databaseAccountGetResults.properties.provisioningState !== "Succeeded";
|
const isUpdateInProgress = databaseAccountGetResults.properties.provisioningState !== "Succeeded";
|
||||||
return {
|
return {
|
||||||
isUpdateInProgress: isUpdateInProgress,
|
isUpdateInProgress: isUpdateInProgress,
|
||||||
notificationMessage: "RefreshMessage",
|
notificationMessage: "Self Serve Example successfully refreshing",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,16 +10,7 @@ import {
|
|||||||
SelfServeNotificationType,
|
SelfServeNotificationType,
|
||||||
SmartUiInput,
|
SmartUiInput,
|
||||||
} from "../SelfServeTypes";
|
} from "../SelfServeTypes";
|
||||||
import {
|
import { onRefreshSelfServeExample, getMaxThroughput, Regions, update, initialize } from "./SelfServeExample.rp";
|
||||||
onRefreshSelfServeExample,
|
|
||||||
Regions,
|
|
||||||
update,
|
|
||||||
initialize,
|
|
||||||
getMinDatabaseThroughput,
|
|
||||||
getMaxDatabaseThroughput,
|
|
||||||
getMinCollectionThroughput,
|
|
||||||
getMaxCollectionThroughput,
|
|
||||||
} from "./SelfServeExample.rp";
|
|
||||||
|
|
||||||
const regionDropdownItems: ChoiceItem[] = [
|
const regionDropdownItems: ChoiceItem[] = [
|
||||||
{ label: "North Central US", key: Regions.NorthCentralUS },
|
{ label: "North Central US", key: Regions.NorthCentralUS },
|
||||||
@@ -28,11 +19,11 @@ const regionDropdownItems: ChoiceItem[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const selfServeExampleInfo: Info = {
|
const selfServeExampleInfo: Info = {
|
||||||
messageTKey: "ClassInfo",
|
message: "This is a self serve class",
|
||||||
};
|
};
|
||||||
|
|
||||||
const regionDropdownInfo: Info = {
|
const regionDropdownInfo: Info = {
|
||||||
messageTKey: "RegionDropdownInfo",
|
message: "More regions can be added in the future.",
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRegionsChange = (currentState: Map<string, SmartUiInput>, newValue: InputType): Map<string, SmartUiInput> => {
|
const onRegionsChange = (currentState: Map<string, SmartUiInput>, newValue: InputType): Map<string, SmartUiInput> => {
|
||||||
@@ -59,7 +50,7 @@ const onEnableDbLevelThroughputChange = (
|
|||||||
|
|
||||||
const validate = (currentvalues: Map<string, SmartUiInput>): void => {
|
const validate = (currentvalues: Map<string, SmartUiInput>): void => {
|
||||||
if (!currentvalues.get("regions").value || !currentvalues.get("accountName").value) {
|
if (!currentvalues.get("regions").value || !currentvalues.get("accountName").value) {
|
||||||
throw new Error("ValidationError");
|
throw new Error("Regions and AccountName should not be empty.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,9 +66,6 @@ const validate = (currentvalues: Map<string, SmartUiInput>): void => {
|
|||||||
|
|
||||||
You can test this self serve UI by using the featureflag '?feature.selfServeType=example'
|
You can test this self serve UI by using the featureflag '?feature.selfServeType=example'
|
||||||
and plumb in similar feature flags for your own self serve class.
|
and plumb in similar feature flags for your own self serve class.
|
||||||
|
|
||||||
All string to be used should be present in the "src/Localization" folder, in the language specific json files. The
|
|
||||||
corresponding key should be given as the value for the fields like "label", the error message etc.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -129,7 +117,7 @@ export default class SelfServeExample extends SelfServeBaseClass {
|
|||||||
let dbThroughput = currentValues.get("dbThroughput")?.value as number;
|
let dbThroughput = currentValues.get("dbThroughput")?.value as number;
|
||||||
dbThroughput = enableDbLevelThroughput ? dbThroughput : undefined;
|
dbThroughput = enableDbLevelThroughput ? dbThroughput : undefined;
|
||||||
await update(regions, enableLogging, accountName, collectionThroughput, dbThroughput);
|
await update(regions, enableLogging, accountName, collectionThroughput, dbThroughput);
|
||||||
return { message: "SubmissionMessage", type: SelfServeNotificationType.info };
|
return { message: "submitted successfully", type: SelfServeNotificationType.info };
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -173,10 +161,10 @@ export default class SelfServeExample extends SelfServeBaseClass {
|
|||||||
*/
|
*/
|
||||||
@Values({
|
@Values({
|
||||||
description: {
|
description: {
|
||||||
textTKey: "DescriptionText",
|
text: "This class sets collection and database throughput.",
|
||||||
link: {
|
link: {
|
||||||
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
||||||
textTKey: "DecriptionLinkText",
|
text: "Click here for more information",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -205,26 +193,26 @@ export default class SelfServeExample extends SelfServeBaseClass {
|
|||||||
any other value of "regions"
|
any other value of "regions"
|
||||||
*/
|
*/
|
||||||
@OnChange(onRegionsChange)
|
@OnChange(onRegionsChange)
|
||||||
@Values({ labelTKey: "Regions", choices: regionDropdownItems, placeholderTKey: "RegionsPlaceholder" })
|
@Values({ label: "Regions", choices: regionDropdownItems, placeholder: "Select a region" })
|
||||||
regions: ChoiceItem;
|
regions: ChoiceItem;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Enable Logging",
|
label: "Enable Logging",
|
||||||
trueLabelTKey: "Enable",
|
trueLabel: "Enable",
|
||||||
falseLabelTKey: "Disable",
|
falseLabel: "Disable",
|
||||||
})
|
})
|
||||||
enableLogging: boolean;
|
enableLogging: boolean;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Account Name",
|
label: "Account Name",
|
||||||
placeholderTKey: "AccountNamePlaceHolder",
|
placeholder: "Enter the account name",
|
||||||
})
|
})
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Collection Throughput",
|
label: "Collection Throughput",
|
||||||
min: getMinCollectionThroughput,
|
min: 400,
|
||||||
max: getMaxCollectionThroughput,
|
max: getMaxThroughput,
|
||||||
step: 100,
|
step: 100,
|
||||||
uiType: NumberUiType.Spinner,
|
uiType: NumberUiType.Spinner,
|
||||||
})
|
})
|
||||||
@@ -236,16 +224,16 @@ export default class SelfServeExample extends SelfServeBaseClass {
|
|||||||
*/
|
*/
|
||||||
@OnChange(onEnableDbLevelThroughputChange)
|
@OnChange(onEnableDbLevelThroughputChange)
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Enable DB level throughput",
|
label: "Enable DB level throughput",
|
||||||
trueLabelTKey: "Enable",
|
trueLabel: "Enable",
|
||||||
falseLabelTKey: "Disable",
|
falseLabel: "Disable",
|
||||||
})
|
})
|
||||||
enableDbLevelThroughput: boolean;
|
enableDbLevelThroughput: boolean;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Database Throughput",
|
label: "Database Throughput",
|
||||||
min: getMinDatabaseThroughput,
|
min: 400,
|
||||||
max: getMaxDatabaseThroughput,
|
max: getMaxThroughput,
|
||||||
step: 100,
|
step: 100,
|
||||||
uiType: NumberUiType.Slider,
|
uiType: NumberUiType.Slider,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,17 +34,17 @@ describe("SelfServeComponent", () => {
|
|||||||
root: {
|
root: {
|
||||||
id: "root",
|
id: "root",
|
||||||
info: {
|
info: {
|
||||||
messageTKey: "Start at $24/mo per database",
|
message: "Start at $24/mo per database",
|
||||||
link: {
|
link: {
|
||||||
href: "https://aka.ms/azure-cosmos-db-pricing",
|
href: "https://aka.ms/azure-cosmos-db-pricing",
|
||||||
textTKey: "More Details",
|
text: "More Details",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "throughput",
|
id: "throughput",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Throughput (input)",
|
label: "Throughput (input)",
|
||||||
dataFieldName: "throughput",
|
dataFieldName: "throughput",
|
||||||
type: "number",
|
type: "number",
|
||||||
min: 400,
|
min: 400,
|
||||||
@@ -57,7 +57,7 @@ describe("SelfServeComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "containerId",
|
id: "containerId",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Container id",
|
label: "Container id",
|
||||||
dataFieldName: "containerId",
|
dataFieldName: "containerId",
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
@@ -65,9 +65,9 @@ describe("SelfServeComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "analyticalStore",
|
id: "analyticalStore",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Analytical Store",
|
label: "Analytical Store",
|
||||||
trueLabelTKey: "Enabled",
|
trueLabel: "Enabled",
|
||||||
falseLabelTKey: "Disabled",
|
falseLabel: "Disabled",
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
dataFieldName: "analyticalStore",
|
dataFieldName: "analyticalStore",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
@@ -76,7 +76,7 @@ describe("SelfServeComponent", () => {
|
|||||||
{
|
{
|
||||||
id: "database",
|
id: "database",
|
||||||
input: {
|
input: {
|
||||||
labelTKey: "Database",
|
label: "Database",
|
||||||
dataFieldName: "database",
|
dataFieldName: "database",
|
||||||
type: "object",
|
type: "object",
|
||||||
choices: [
|
choices: [
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ import {
|
|||||||
} from "./SelfServeTypes";
|
} from "./SelfServeTypes";
|
||||||
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||||
import { getMessageBarType } from "./SelfServeUtils";
|
import { getMessageBarType } from "./SelfServeUtils";
|
||||||
import { Translation } from "react-i18next";
|
|
||||||
import { TFunction } from "i18next";
|
|
||||||
import "../i18n";
|
|
||||||
|
|
||||||
export interface SelfServeComponentProps {
|
export interface SelfServeComponentProps {
|
||||||
descriptor: SelfServeDescriptor;
|
descriptor: SelfServeDescriptor;
|
||||||
@@ -46,8 +43,6 @@ export interface SelfServeComponentState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SelfServeComponent extends React.Component<SelfServeComponentProps, SelfServeComponentState> {
|
export class SelfServeComponent extends React.Component<SelfServeComponentProps, SelfServeComponentState> {
|
||||||
private smartUiGeneratorClassName: string;
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.performRefresh();
|
this.performRefresh();
|
||||||
this.initializeSmartUiComponent();
|
this.initializeSmartUiComponent();
|
||||||
@@ -65,7 +60,6 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
notification: undefined,
|
notification: undefined,
|
||||||
refreshResult: undefined,
|
refreshResult: undefined,
|
||||||
};
|
};
|
||||||
this.smartUiGeneratorClassName = this.props.descriptor.root.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onError = (hasErrors: boolean): void => {
|
private onError = (hasErrors: boolean): void => {
|
||||||
@@ -153,8 +147,8 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
currentValues: Map<string, SmartUiInput>,
|
currentValues: Map<string, SmartUiInput>,
|
||||||
baselineValues: Map<string, SmartUiInput>
|
baselineValues: Map<string, SmartUiInput>
|
||||||
): Promise<AnyDisplay> => {
|
): Promise<AnyDisplay> => {
|
||||||
input.labelTKey = await this.getResolvedValue(input.labelTKey);
|
input.label = await this.getResolvedValue(input.label);
|
||||||
input.placeholderTKey = await this.getResolvedValue(input.placeholderTKey);
|
input.placeholder = await this.getResolvedValue(input.placeholder);
|
||||||
|
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case "string": {
|
case "string": {
|
||||||
@@ -183,8 +177,8 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
}
|
}
|
||||||
case "boolean": {
|
case "boolean": {
|
||||||
const booleanInput = input as BooleanInput;
|
const booleanInput = input as BooleanInput;
|
||||||
booleanInput.trueLabelTKey = await this.getResolvedValue(booleanInput.trueLabelTKey);
|
booleanInput.trueLabel = await this.getResolvedValue(booleanInput.trueLabel);
|
||||||
booleanInput.falseLabelTKey = await this.getResolvedValue(booleanInput.falseLabelTKey);
|
booleanInput.falseLabel = await this.getResolvedValue(booleanInput.falseLabel);
|
||||||
return booleanInput;
|
return booleanInput;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -220,7 +214,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
onSavePromise.catch((error) => {
|
onSavePromise.catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
notification: {
|
notification: {
|
||||||
message: `${error.message}`,
|
message: `Error: ${error.message}`,
|
||||||
type: SelfServeNotificationType.error,
|
type: SelfServeNotificationType.error,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -279,15 +273,11 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
this.setState({ isInitializing: false });
|
this.setState({ isInitializing: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
public getCommonTranslation = (translationFunction: TFunction, key: string): string => {
|
private getCommandBarItems = (): ICommandBarItemProps[] => {
|
||||||
return translationFunction(`Common.${key}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
private getCommandBarItems = (translate: TFunction): ICommandBarItemProps[] => {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: "save",
|
key: "save",
|
||||||
text: this.getCommonTranslation(translate, "Save"),
|
text: "Save",
|
||||||
iconProps: { iconName: "Save" },
|
iconProps: { iconName: "Save" },
|
||||||
split: true,
|
split: true,
|
||||||
disabled: this.isSaveButtonDisabled(),
|
disabled: this.isSaveButtonDisabled(),
|
||||||
@@ -295,7 +285,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "discard",
|
key: "discard",
|
||||||
text: this.getCommonTranslation(translate, "Discard"),
|
text: "Discard",
|
||||||
iconProps: { iconName: "Undo" },
|
iconProps: { iconName: "Undo" },
|
||||||
split: true,
|
split: true,
|
||||||
disabled: this.isDiscardButtonDisabled(),
|
disabled: this.isDiscardButtonDisabled(),
|
||||||
@@ -305,7 +295,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "refresh",
|
key: "refresh",
|
||||||
text: this.getCommonTranslation(translate, "Refresh"),
|
text: "Refresh",
|
||||||
disabled: this.state.isInitializing,
|
disabled: this.state.isInitializing,
|
||||||
iconProps: { iconName: "Refresh" },
|
iconProps: { iconName: "Refresh" },
|
||||||
split: true,
|
split: true,
|
||||||
@@ -316,66 +306,47 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
private getNotificationMessageTranslation = (translationFunction: TFunction, messageKey: string): string => {
|
|
||||||
const translation = translationFunction(messageKey);
|
|
||||||
if (translation === `${this.smartUiGeneratorClassName}.${messageKey}`) {
|
|
||||||
return messageKey;
|
|
||||||
}
|
|
||||||
return translation;
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const containerStackTokens: IStackTokens = { childrenGap: 5 };
|
const containerStackTokens: IStackTokens = { childrenGap: 5 };
|
||||||
if (this.state.compileErrorMessage) {
|
if (this.state.compileErrorMessage) {
|
||||||
return <MessageBar messageBarType={MessageBarType.error}>{this.state.compileErrorMessage}</MessageBar>;
|
return <MessageBar messageBarType={MessageBarType.error}>{this.state.compileErrorMessage}</MessageBar>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Translation>
|
<div style={{ overflowX: "auto" }}>
|
||||||
{(translate) => {
|
<Stack tokens={containerStackTokens} styles={{ root: { padding: 10 } }}>
|
||||||
const getTranslation = (key: string): string => {
|
<CommandBar styles={{ root: { paddingLeft: 0 } }} items={this.getCommandBarItems()} />
|
||||||
return translate(`${this.smartUiGeneratorClassName}.${key}`);
|
{this.state.isInitializing ? (
|
||||||
};
|
<Spinner
|
||||||
|
size={SpinnerSize.large}
|
||||||
return (
|
styles={{ root: { textAlign: "center", justifyContent: "center", width: "100%", height: "100%" } }}
|
||||||
<div style={{ overflowX: "auto" }}>
|
/>
|
||||||
<Stack tokens={containerStackTokens} styles={{ root: { padding: 10 } }}>
|
) : (
|
||||||
<CommandBar styles={{ root: { paddingLeft: 0 } }} items={this.getCommandBarItems(translate)} />
|
<>
|
||||||
{this.state.isInitializing ? (
|
{this.state.refreshResult?.isUpdateInProgress && (
|
||||||
<Spinner
|
<MessageBar messageBarType={MessageBarType.info} styles={{ root: { width: 400 } }}>
|
||||||
size={SpinnerSize.large}
|
{this.state.refreshResult.notificationMessage}
|
||||||
styles={{ root: { textAlign: "center", justifyContent: "center", width: "100%", height: "100%" } }}
|
</MessageBar>
|
||||||
/>
|
)}
|
||||||
) : (
|
{this.state.notification && (
|
||||||
<>
|
<MessageBar
|
||||||
{this.state.refreshResult?.isUpdateInProgress && (
|
messageBarType={getMessageBarType(this.state.notification.type)}
|
||||||
<MessageBar messageBarType={MessageBarType.info} styles={{ root: { width: 400 } }}>
|
styles={{ root: { width: 400 } }}
|
||||||
{getTranslation(this.state.refreshResult.notificationMessage)}
|
onDismiss={() => this.setState({ notification: undefined })}
|
||||||
</MessageBar>
|
>
|
||||||
)}
|
{this.state.notification.message}
|
||||||
{this.state.notification && (
|
</MessageBar>
|
||||||
<MessageBar
|
)}
|
||||||
messageBarType={getMessageBarType(this.state.notification.type)}
|
<SmartUiComponent
|
||||||
styles={{ root: { width: 400 } }}
|
disabled={this.state.refreshResult?.isUpdateInProgress}
|
||||||
onDismiss={() => this.setState({ notification: undefined })}
|
descriptor={this.state.root as SmartUiDescriptor}
|
||||||
>
|
currentValues={this.state.currentValues}
|
||||||
{this.getNotificationMessageTranslation(getTranslation, this.state.notification.message)}
|
onInputChange={this.onInputChange}
|
||||||
</MessageBar>
|
onError={this.onError}
|
||||||
)}
|
/>
|
||||||
<SmartUiComponent
|
</>
|
||||||
disabled={this.state.refreshResult?.isUpdateInProgress}
|
)}
|
||||||
descriptor={this.state.root as SmartUiDescriptor}
|
</Stack>
|
||||||
currentValues={this.state.currentValues}
|
</div>
|
||||||
onInputChange={this.onInputChange}
|
|
||||||
onError={this.onError}
|
|
||||||
getTranslation={getTranslation}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Translation>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ interface BaseInput {
|
|||||||
dataFieldName: string;
|
dataFieldName: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
type: InputTypeValue;
|
type: InputTypeValue;
|
||||||
labelTKey?: (() => Promise<string>) | string;
|
label?: (() => Promise<string>) | string;
|
||||||
onChange?: (currentState: Map<string, SmartUiInput>, newValue: InputType) => Map<string, SmartUiInput>;
|
onChange?: (currentState: Map<string, SmartUiInput>, newValue: InputType) => Map<string, SmartUiInput>;
|
||||||
placeholderTKey?: (() => Promise<string>) | string;
|
placeholder?: (() => Promise<string>) | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NumberInput extends BaseInput {
|
export interface NumberInput extends BaseInput {
|
||||||
@@ -16,8 +16,8 @@ export interface NumberInput extends BaseInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BooleanInput extends BaseInput {
|
export interface BooleanInput extends BaseInput {
|
||||||
trueLabelTKey: (() => Promise<string>) | string;
|
trueLabel: (() => Promise<string>) | string;
|
||||||
falseLabelTKey: (() => Promise<string>) | string;
|
falseLabel: (() => Promise<string>) | string;
|
||||||
defaultValue?: boolean;
|
defaultValue?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,18 +92,18 @@ export type ChoiceItem = { label: string; key: string };
|
|||||||
export type InputType = number | string | boolean | ChoiceItem;
|
export type InputType = number | string | boolean | ChoiceItem;
|
||||||
|
|
||||||
export interface Info {
|
export interface Info {
|
||||||
messageTKey: string;
|
message: string;
|
||||||
link?: {
|
link?: {
|
||||||
href: string;
|
href: string;
|
||||||
textTKey: string;
|
text: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Description {
|
export interface Description {
|
||||||
textTKey: string;
|
text: string;
|
||||||
link?: {
|
link?: {
|
||||||
href: string;
|
href: string;
|
||||||
textTKey: string;
|
text: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "dbThroughput",
|
id: "dbThroughput",
|
||||||
dataFieldName: "dbThroughput",
|
dataFieldName: "dbThroughput",
|
||||||
type: "number",
|
type: "number",
|
||||||
labelTKey: "Database Throughput",
|
label: "Database Throughput",
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -71,7 +71,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "collThroughput",
|
id: "collThroughput",
|
||||||
dataFieldName: "collThroughput",
|
dataFieldName: "collThroughput",
|
||||||
type: "number",
|
type: "number",
|
||||||
labelTKey: "Coll Throughput",
|
label: "Coll Throughput",
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -84,7 +84,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "invalidThroughput",
|
id: "invalidThroughput",
|
||||||
dataFieldName: "invalidThroughput",
|
dataFieldName: "invalidThroughput",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Invalid Coll Throughput",
|
label: "Invalid Coll Throughput",
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -98,8 +98,8 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "collName",
|
id: "collName",
|
||||||
dataFieldName: "collName",
|
dataFieldName: "collName",
|
||||||
type: "string",
|
type: "string",
|
||||||
labelTKey: "Coll Name",
|
label: "Coll Name",
|
||||||
placeholderTKey: "placeholder text",
|
placeholder: "placeholder text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -108,9 +108,9 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "enableLogging",
|
id: "enableLogging",
|
||||||
dataFieldName: "enableLogging",
|
dataFieldName: "enableLogging",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Enable Logging",
|
label: "Enable Logging",
|
||||||
trueLabelTKey: "Enable",
|
trueLabel: "Enable",
|
||||||
falseLabelTKey: "Disable",
|
falseLabel: "Disable",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -119,8 +119,8 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "invalidEnableLogging",
|
id: "invalidEnableLogging",
|
||||||
dataFieldName: "invalidEnableLogging",
|
dataFieldName: "invalidEnableLogging",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Invalid Enable Logging",
|
label: "Invalid Enable Logging",
|
||||||
placeholderTKey: "placeholder text",
|
placeholder: "placeholder text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -129,7 +129,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "regions",
|
id: "regions",
|
||||||
dataFieldName: "regions",
|
dataFieldName: "regions",
|
||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Regions",
|
label: "Regions",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "South West US", key: "SWUS" },
|
{ label: "South West US", key: "SWUS" },
|
||||||
{ label: "North Central US", key: "NCUS" },
|
{ label: "North Central US", key: "NCUS" },
|
||||||
@@ -143,14 +143,14 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "invalidRegions",
|
id: "invalidRegions",
|
||||||
dataFieldName: "invalidRegions",
|
dataFieldName: "invalidRegions",
|
||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Invalid Regions",
|
label: "Invalid Regions",
|
||||||
placeholderTKey: "placeholder text",
|
placeholder: "placeholder text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
const expectedDescriptor = {
|
const expectedDescriptor = {
|
||||||
root: {
|
root: {
|
||||||
id: "TestClass",
|
id: "root",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "dbThroughput",
|
id: "dbThroughput",
|
||||||
@@ -158,7 +158,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "dbThroughput",
|
id: "dbThroughput",
|
||||||
dataFieldName: "dbThroughput",
|
dataFieldName: "dbThroughput",
|
||||||
type: "number",
|
type: "number",
|
||||||
labelTKey: "Database Throughput",
|
label: "Database Throughput",
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -172,7 +172,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "collThroughput",
|
id: "collThroughput",
|
||||||
dataFieldName: "collThroughput",
|
dataFieldName: "collThroughput",
|
||||||
type: "number",
|
type: "number",
|
||||||
labelTKey: "Coll Throughput",
|
label: "Coll Throughput",
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -186,7 +186,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "invalidThroughput",
|
id: "invalidThroughput",
|
||||||
dataFieldName: "invalidThroughput",
|
dataFieldName: "invalidThroughput",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Invalid Coll Throughput",
|
label: "Invalid Coll Throughput",
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -201,8 +201,8 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "collName",
|
id: "collName",
|
||||||
dataFieldName: "collName",
|
dataFieldName: "collName",
|
||||||
type: "string",
|
type: "string",
|
||||||
labelTKey: "Coll Name",
|
label: "Coll Name",
|
||||||
placeholderTKey: "placeholder text",
|
placeholder: "placeholder text",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
},
|
},
|
||||||
@@ -212,9 +212,9 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "enableLogging",
|
id: "enableLogging",
|
||||||
dataFieldName: "enableLogging",
|
dataFieldName: "enableLogging",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Enable Logging",
|
label: "Enable Logging",
|
||||||
trueLabelTKey: "Enable",
|
trueLabel: "Enable",
|
||||||
falseLabelTKey: "Disable",
|
falseLabel: "Disable",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
},
|
},
|
||||||
@@ -224,8 +224,8 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "invalidEnableLogging",
|
id: "invalidEnableLogging",
|
||||||
dataFieldName: "invalidEnableLogging",
|
dataFieldName: "invalidEnableLogging",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Invalid Enable Logging",
|
label: "Invalid Enable Logging",
|
||||||
placeholderTKey: "placeholder text",
|
placeholder: "placeholder text",
|
||||||
errorMessage: "label, truelabel and falselabel are required for boolean input 'invalidEnableLogging'.",
|
errorMessage: "label, truelabel and falselabel are required for boolean input 'invalidEnableLogging'.",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
@@ -236,7 +236,7 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "regions",
|
id: "regions",
|
||||||
dataFieldName: "regions",
|
dataFieldName: "regions",
|
||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Regions",
|
label: "Regions",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "South West US", key: "SWUS" },
|
{ label: "South West US", key: "SWUS" },
|
||||||
{ label: "North Central US", key: "NCUS" },
|
{ label: "North Central US", key: "NCUS" },
|
||||||
@@ -251,8 +251,8 @@ describe("SelfServeUtils", () => {
|
|||||||
id: "invalidRegions",
|
id: "invalidRegions",
|
||||||
dataFieldName: "invalidRegions",
|
dataFieldName: "invalidRegions",
|
||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Invalid Regions",
|
label: "Invalid Regions",
|
||||||
placeholderTKey: "placeholder text",
|
placeholder: "placeholder text",
|
||||||
errorMessage: "label and choices are required for Choice input 'invalidRegions'.",
|
errorMessage: "label and choices are required for Choice input 'invalidRegions'.",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
@@ -270,7 +270,7 @@ describe("SelfServeUtils", () => {
|
|||||||
"invalidRegions",
|
"invalidRegions",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const descriptor = mapToSmartUiDescriptor("TestClass", context);
|
const descriptor = mapToSmartUiDescriptor(context);
|
||||||
expect(descriptor).toEqual(expectedDescriptor);
|
expect(descriptor).toEqual(expectedDescriptor);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ export interface DecoratorProperties {
|
|||||||
id: string;
|
id: string;
|
||||||
info?: (() => Promise<Info>) | Info;
|
info?: (() => Promise<Info>) | Info;
|
||||||
type?: InputTypeValue;
|
type?: InputTypeValue;
|
||||||
labelTKey?: (() => Promise<string>) | string;
|
label?: (() => Promise<string>) | string;
|
||||||
placeholderTKey?: (() => Promise<string>) | string;
|
placeholder?: (() => Promise<string>) | string;
|
||||||
dataFieldName?: string;
|
dataFieldName?: string;
|
||||||
min?: (() => Promise<number>) | number;
|
min?: (() => Promise<number>) | number;
|
||||||
max?: (() => Promise<number>) | number;
|
max?: (() => Promise<number>) | number;
|
||||||
step?: (() => Promise<number>) | number;
|
step?: (() => Promise<number>) | number;
|
||||||
trueLabelTKey?: (() => Promise<string>) | string;
|
trueLabel?: (() => Promise<string>) | string;
|
||||||
falseLabelTKey?: (() => Promise<string>) | string;
|
falseLabel?: (() => Promise<string>) | string;
|
||||||
choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
||||||
uiType?: string;
|
uiType?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
@@ -100,21 +100,18 @@ export const updateContextWithDecorator = <T extends keyof DecoratorProperties,
|
|||||||
|
|
||||||
export const buildSmartUiDescriptor = (className: string, target: unknown): void => {
|
export const buildSmartUiDescriptor = (className: string, target: unknown): void => {
|
||||||
const context = Reflect.getMetadata(className, target) as Map<string, DecoratorProperties>;
|
const context = Reflect.getMetadata(className, target) as Map<string, DecoratorProperties>;
|
||||||
const smartUiDescriptor = mapToSmartUiDescriptor(className, context);
|
const smartUiDescriptor = mapToSmartUiDescriptor(context);
|
||||||
Reflect.defineMetadata(className, smartUiDescriptor, target);
|
Reflect.defineMetadata(className, smartUiDescriptor, target);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapToSmartUiDescriptor = (
|
export const mapToSmartUiDescriptor = (context: Map<string, DecoratorProperties>): SelfServeDescriptor => {
|
||||||
className: string,
|
|
||||||
context: Map<string, DecoratorProperties>
|
|
||||||
): SelfServeDescriptor => {
|
|
||||||
const root = context.get("root");
|
const root = context.get("root");
|
||||||
context.delete("root");
|
context.delete("root");
|
||||||
const inputNames: string[] = [];
|
const inputNames: string[] = [];
|
||||||
|
|
||||||
const smartUiDescriptor: SelfServeDescriptor = {
|
const smartUiDescriptor: SelfServeDescriptor = {
|
||||||
root: {
|
root: {
|
||||||
id: className,
|
id: "root",
|
||||||
info: root?.info,
|
info: root?.info,
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
@@ -150,7 +147,7 @@ const addToDescriptor = (
|
|||||||
const getInput = (value: DecoratorProperties): AnyDisplay => {
|
const getInput = (value: DecoratorProperties): AnyDisplay => {
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
case "number":
|
case "number":
|
||||||
if (!value.labelTKey || !value.step || !value.uiType || !value.min || !value.max) {
|
if (!value.label || !value.step || !value.uiType || !value.min || !value.max) {
|
||||||
value.errorMessage = `label, step, min, max and uiType are required for number input '${value.id}'.`;
|
value.errorMessage = `label, step, min, max and uiType are required for number input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as NumberInput;
|
return value as NumberInput;
|
||||||
@@ -158,17 +155,17 @@ const getInput = (value: DecoratorProperties): AnyDisplay => {
|
|||||||
if (value.description) {
|
if (value.description) {
|
||||||
return value as DescriptionDisplay;
|
return value as DescriptionDisplay;
|
||||||
}
|
}
|
||||||
if (!value.labelTKey) {
|
if (!value.label) {
|
||||||
value.errorMessage = `label is required for string input '${value.id}'.`;
|
value.errorMessage = `label is required for string input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as StringInput;
|
return value as StringInput;
|
||||||
case "boolean":
|
case "boolean":
|
||||||
if (!value.labelTKey || !value.trueLabelTKey || !value.falseLabelTKey) {
|
if (!value.label || !value.trueLabel || !value.falseLabel) {
|
||||||
value.errorMessage = `label, truelabel and falselabel are required for boolean input '${value.id}'.`;
|
value.errorMessage = `label, truelabel and falselabel are required for boolean input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as BooleanInput;
|
return value as BooleanInput;
|
||||||
default:
|
default:
|
||||||
if (!value.labelTKey || !value.choices) {
|
if (!value.label || !value.choices) {
|
||||||
value.errorMessage = `label and choices are required for Choice input '${value.id}'.`;
|
value.errorMessage = `label and choices are required for Choice input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as ChoiceInput;
|
return value as ChoiceInput;
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
description: {
|
description: {
|
||||||
textTKey: "Provisioning dedicated gateways for SqlX accounts.",
|
text: "Provisioning dedicated gateways for SqlX accounts.",
|
||||||
link: {
|
link: {
|
||||||
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
||||||
textTKey: "Learn more about dedicated gateway.",
|
text: "Learn more about dedicated gateway.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -73,21 +73,21 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
|
|
||||||
@OnChange(onEnableDedicatedGatewayChange)
|
@OnChange(onEnableDedicatedGatewayChange)
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Dedicated Gateway",
|
label: "Dedicated Gateway",
|
||||||
trueLabelTKey: "Enable",
|
trueLabel: "Enable",
|
||||||
falseLabelTKey: "Disable",
|
falseLabel: "Disable",
|
||||||
})
|
})
|
||||||
enableDedicatedGateway: boolean;
|
enableDedicatedGateway: boolean;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "SKUs",
|
label: "SKUs",
|
||||||
choices: getSkus,
|
choices: getSkus,
|
||||||
placeholderTKey: "Select SKUs",
|
placeholder: "Select SKUs",
|
||||||
})
|
})
|
||||||
sku: ChoiceItem;
|
sku: ChoiceItem;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Number of instances",
|
label: "Number of instances",
|
||||||
min: getInstancesMin,
|
min: getInstancesMin,
|
||||||
max: getInstancesMax,
|
max: getInstancesMax,
|
||||||
step: 1,
|
step: 1,
|
||||||
|
|||||||
@@ -1,21 +1,686 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
||||||
<Translation>
|
<div
|
||||||
<Component />
|
style={
|
||||||
</Translation>
|
Object {
|
||||||
|
"overflowX": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"padding": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledCommandBarBase
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Save",
|
||||||
|
},
|
||||||
|
"key": "save",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Save",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Undo",
|
||||||
|
},
|
||||||
|
"key": "discard",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Discard",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": false,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Refresh",
|
||||||
|
},
|
||||||
|
"key": "refresh",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Refresh",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingLeft": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={0}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 400,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
refresh performed successfully
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={0}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 400,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
submitted successfully
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
<SmartUiComponent
|
||||||
|
currentValues={
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor={
|
||||||
|
Object {
|
||||||
|
"initialize": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"inputNames": Array [
|
||||||
|
"throughput",
|
||||||
|
"analyticalStore",
|
||||||
|
"database",
|
||||||
|
],
|
||||||
|
"onRefresh": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"onSave": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"root": Object {
|
||||||
|
"children": Array [
|
||||||
|
Object {
|
||||||
|
"id": "throughput",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "throughput",
|
||||||
|
"defaultValue": 400,
|
||||||
|
"label": "Throughput (input)",
|
||||||
|
"max": 500,
|
||||||
|
"min": 400,
|
||||||
|
"placeholder": undefined,
|
||||||
|
"step": 10,
|
||||||
|
"type": "number",
|
||||||
|
"uiType": "Spinner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "containerId",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "containerId",
|
||||||
|
"label": "Container id",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "analyticalStore",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "analyticalStore",
|
||||||
|
"defaultValue": true,
|
||||||
|
"falseLabel": "Disabled",
|
||||||
|
"label": "Analytical Store",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"trueLabel": "Enabled",
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "database",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"choices": Array [
|
||||||
|
Object {
|
||||||
|
"key": "db1",
|
||||||
|
"label": "Database 1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "db2",
|
||||||
|
"label": "Database 2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "db3",
|
||||||
|
"label": "Database 3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"dataFieldName": "database",
|
||||||
|
"defaultKey": "db2",
|
||||||
|
"label": "Database",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "root",
|
||||||
|
"info": Object {
|
||||||
|
"link": Object {
|
||||||
|
"href": "https://aka.ms/azure-cosmos-db-pricing",
|
||||||
|
"text": "More Details",
|
||||||
|
},
|
||||||
|
"message": "Start at $24/mo per database",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disabled={true}
|
||||||
|
onError={[Function]}
|
||||||
|
onInputChange={[Function]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
||||||
<Translation>
|
<div
|
||||||
<Component />
|
style={
|
||||||
</Translation>
|
Object {
|
||||||
|
"overflowX": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"padding": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledCommandBarBase
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Save",
|
||||||
|
},
|
||||||
|
"key": "save",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Save",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Undo",
|
||||||
|
},
|
||||||
|
"key": "discard",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Discard",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": false,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Refresh",
|
||||||
|
},
|
||||||
|
"key": "refresh",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Refresh",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingLeft": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={0}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 400,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
submitted successfully
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
<SmartUiComponent
|
||||||
|
currentValues={
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor={
|
||||||
|
Object {
|
||||||
|
"initialize": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"inputNames": Array [
|
||||||
|
"throughput",
|
||||||
|
"analyticalStore",
|
||||||
|
"database",
|
||||||
|
],
|
||||||
|
"onRefresh": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
Array [],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"onSave": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"root": Object {
|
||||||
|
"children": Array [
|
||||||
|
Object {
|
||||||
|
"id": "throughput",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "throughput",
|
||||||
|
"defaultValue": 400,
|
||||||
|
"label": "Throughput (input)",
|
||||||
|
"max": 500,
|
||||||
|
"min": 400,
|
||||||
|
"placeholder": undefined,
|
||||||
|
"step": 10,
|
||||||
|
"type": "number",
|
||||||
|
"uiType": "Spinner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "containerId",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "containerId",
|
||||||
|
"label": "Container id",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "analyticalStore",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "analyticalStore",
|
||||||
|
"defaultValue": true,
|
||||||
|
"falseLabel": "Disabled",
|
||||||
|
"label": "Analytical Store",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"trueLabel": "Enabled",
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "database",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"choices": Array [
|
||||||
|
Object {
|
||||||
|
"key": "db1",
|
||||||
|
"label": "Database 1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "db2",
|
||||||
|
"label": "Database 2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "db3",
|
||||||
|
"label": "Database 3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"dataFieldName": "database",
|
||||||
|
"defaultKey": "db2",
|
||||||
|
"label": "Database",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "root",
|
||||||
|
"info": Object {
|
||||||
|
"link": Object {
|
||||||
|
"href": "https://aka.ms/azure-cosmos-db-pricing",
|
||||||
|
"text": "More Details",
|
||||||
|
},
|
||||||
|
"message": "Start at $24/mo per database",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disabled={false}
|
||||||
|
onError={[Function]}
|
||||||
|
onInputChange={[Function]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 3`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 3`] = `
|
||||||
<Translation>
|
<div
|
||||||
<Component />
|
style={
|
||||||
</Translation>
|
Object {
|
||||||
|
"overflowX": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"padding": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledCommandBarBase
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Save",
|
||||||
|
},
|
||||||
|
"key": "save",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Save",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Undo",
|
||||||
|
},
|
||||||
|
"key": "discard",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Discard",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Refresh",
|
||||||
|
},
|
||||||
|
"key": "refresh",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Refresh",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingLeft": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledSpinnerBase
|
||||||
|
size={3}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"height": "100%",
|
||||||
|
"justifyContent": "center",
|
||||||
|
"textAlign": "center",
|
||||||
|
"width": "100%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 4`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 4`] = `
|
||||||
@@ -27,7 +692,195 @@ exports[`SelfServeComponent message bar and spinner snapshots 4`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SelfServeComponent should render and honor save, discard, refresh actions 1`] = `
|
exports[`SelfServeComponent should render and honor save, discard, refresh actions 1`] = `
|
||||||
<Translation>
|
<div
|
||||||
<Component />
|
style={
|
||||||
</Translation>
|
Object {
|
||||||
|
"overflowX": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"padding": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledCommandBarBase
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Save",
|
||||||
|
},
|
||||||
|
"key": "save",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Save",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": true,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Undo",
|
||||||
|
},
|
||||||
|
"key": "discard",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Discard",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"disabled": false,
|
||||||
|
"iconProps": Object {
|
||||||
|
"iconName": "Refresh",
|
||||||
|
},
|
||||||
|
"key": "refresh",
|
||||||
|
"onClick": [Function],
|
||||||
|
"split": true,
|
||||||
|
"text": "Refresh",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingLeft": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<SmartUiComponent
|
||||||
|
currentValues={
|
||||||
|
Map {
|
||||||
|
"throughput" => Object {
|
||||||
|
"value": 450,
|
||||||
|
},
|
||||||
|
"analyticalStore" => Object {
|
||||||
|
"value": false,
|
||||||
|
},
|
||||||
|
"database" => Object {
|
||||||
|
"value": "db2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor={
|
||||||
|
Object {
|
||||||
|
"initialize": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"inputNames": Array [
|
||||||
|
"throughput",
|
||||||
|
"analyticalStore",
|
||||||
|
"database",
|
||||||
|
],
|
||||||
|
"onRefresh": [MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": Promise {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"onSave": [MockFunction],
|
||||||
|
"root": Object {
|
||||||
|
"children": Array [
|
||||||
|
Object {
|
||||||
|
"id": "throughput",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "throughput",
|
||||||
|
"defaultValue": 400,
|
||||||
|
"label": "Throughput (input)",
|
||||||
|
"max": 500,
|
||||||
|
"min": 400,
|
||||||
|
"placeholder": undefined,
|
||||||
|
"step": 10,
|
||||||
|
"type": "number",
|
||||||
|
"uiType": "Spinner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "containerId",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "containerId",
|
||||||
|
"label": "Container id",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "analyticalStore",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"dataFieldName": "analyticalStore",
|
||||||
|
"defaultValue": true,
|
||||||
|
"falseLabel": "Disabled",
|
||||||
|
"label": "Analytical Store",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"trueLabel": "Enabled",
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "database",
|
||||||
|
"info": undefined,
|
||||||
|
"input": Object {
|
||||||
|
"choices": Array [
|
||||||
|
Object {
|
||||||
|
"key": "db1",
|
||||||
|
"label": "Database 1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "db2",
|
||||||
|
"label": "Database 2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "db3",
|
||||||
|
"label": "Database 3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"dataFieldName": "database",
|
||||||
|
"defaultKey": "db2",
|
||||||
|
"label": "Database",
|
||||||
|
"placeholder": undefined,
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "root",
|
||||||
|
"info": Object {
|
||||||
|
"link": Object {
|
||||||
|
"href": "https://aka.ms/azure-cosmos-db-pricing",
|
||||||
|
"text": "More Details",
|
||||||
|
},
|
||||||
|
"message": "Start at $24/mo per database",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disabled={false}
|
||||||
|
onError={[Function]}
|
||||||
|
onInputChange={[Function]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -41,13 +41,12 @@ export function useKnockoutExplorer(config: ConfigContext, explorerParams: Explo
|
|||||||
if (config) {
|
if (config) {
|
||||||
if (config.platform === Platform.Hosted) {
|
if (config.platform === Platform.Hosted) {
|
||||||
await configureHosted(config);
|
await configureHosted(config);
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
} else if (config.platform === Platform.Emulator) {
|
} else if (config.platform === Platform.Emulator) {
|
||||||
configureEmulator();
|
configureEmulator();
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
} else if (config.platform === Platform.Portal) {
|
} else if (config.platform === Platform.Portal) {
|
||||||
configurePortal();
|
configurePortal();
|
||||||
}
|
}
|
||||||
|
applyExplorerBindings(explorer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
effect();
|
effect();
|
||||||
@@ -238,14 +237,13 @@ function configurePortal() {
|
|||||||
);
|
);
|
||||||
console.dir(message);
|
console.dir(message);
|
||||||
explorer.configure(message);
|
explorer.configure(message);
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the Portal, configuration of Explorer happens via iframe message
|
// In the Portal, configuration of Explorer happens via iframe message
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message",
|
||||||
(event) => {
|
(event) => {
|
||||||
|
console.dir(event);
|
||||||
if (isInvalidParentFrameOrigin(event)) {
|
if (isInvalidParentFrameOrigin(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -267,7 +265,6 @@ function configurePortal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
explorer.configure(inputs);
|
explorer.configure(inputs);
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
|
|||||||
31
src/i18n.ts
31
src/i18n.ts
@@ -1,31 +0,0 @@
|
|||||||
import i18n from "i18next";
|
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
import XHR from "i18next-http-backend";
|
|
||||||
import EnglishTranslations from "./Localization/en/translations.json";
|
|
||||||
|
|
||||||
i18n
|
|
||||||
.use(XHR)
|
|
||||||
.use(LanguageDetector)
|
|
||||||
.use(initReactI18next)
|
|
||||||
.init({
|
|
||||||
resources: {
|
|
||||||
en: EnglishTranslations,
|
|
||||||
},
|
|
||||||
fallbackLng: "en",
|
|
||||||
detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] },
|
|
||||||
debug: process.env.NODE_ENV === "development",
|
|
||||||
ns: ["translations"],
|
|
||||||
defaultNS: "translations",
|
|
||||||
keySeparator: ".",
|
|
||||||
interpolation: {
|
|
||||||
formatSeparator: ",",
|
|
||||||
},
|
|
||||||
react: {
|
|
||||||
wait: true,
|
|
||||||
bindI18n: "languageChanged loaded",
|
|
||||||
bindI18nStore: "added removed",
|
|
||||||
nsMode: "default",
|
|
||||||
useSuspense: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -37,7 +37,6 @@ export const uploadNotebookIfNotExist = async (frame: Frame, notebookName: strin
|
|||||||
|
|
||||||
export const getNotebookNode = async (frame: Frame, uploadNotebookName: string): Promise<ElementHandle<Element>> => {
|
export const getNotebookNode = async (frame: Frame, uploadNotebookName: string): Promise<ElementHandle<Element>> => {
|
||||||
const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree");
|
const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree");
|
||||||
await frame.waitFor(RENDER_DELAY);
|
|
||||||
let currentNotebookNode: ElementHandle<Element>;
|
let currentNotebookNode: ElementHandle<Element>;
|
||||||
|
|
||||||
const treeNodeHeaders = await notebookResourceTree.$$(".treeNodeHeader");
|
const treeNodeHeaders = await notebookResourceTree.$$(".treeNodeHeader");
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
const { CosmosClient } = require("@azure/cosmos");
|
|
||||||
|
|
||||||
// TODO: Add support for other API connection strings
|
|
||||||
const mongoRegex = RegExp("mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com");
|
|
||||||
|
|
||||||
const connectionString = process.env.PORTAL_RUNNER_CONNECTION_STRING;
|
|
||||||
|
|
||||||
async function cleanup() {
|
|
||||||
if (!connectionString) {
|
|
||||||
throw new Error("Connection string not provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
let client;
|
|
||||||
switch (true) {
|
|
||||||
case connectionString.includes("mongodb://"): {
|
|
||||||
const [, key, accountName] = connectionString.match(mongoRegex);
|
|
||||||
client = new CosmosClient({
|
|
||||||
key,
|
|
||||||
endpoint: `https://${accountName}.documents.azure.com:443/`,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// TODO: Add support for other API connection strings
|
|
||||||
default:
|
|
||||||
client = new CosmosClient(connectionString);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await client.databases.readAll().fetchAll();
|
|
||||||
return Promise.all(
|
|
||||||
response.resources.map(async (db) => {
|
|
||||||
const dbTimestamp = new Date(db._ts * 1000);
|
|
||||||
const twentyMinutesAgo = new Date(Date.now() - 1000 * 60 * 20);
|
|
||||||
if (dbTimestamp < twentyMinutesAgo) {
|
|
||||||
await client.database(db.id).delete();
|
|
||||||
console.log(`DELETED: ${db.id} | Timestamp: ${dbTimestamp}`);
|
|
||||||
} else {
|
|
||||||
console.log(`SKIPPED: ${db.id} | Timestamp: ${dbTimestamp}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
.then(() => {
|
|
||||||
process.exit(0);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user