resolve merge master conflict

This commit is contained in:
hardiknai-techm
2021-03-31 17:08:50 +05:30
52 changed files with 1570 additions and 1508 deletions

View File

@@ -253,11 +253,8 @@ src/Terminal/NotebookAppContracts.d.ts
src/Terminal/index.ts src/Terminal/index.ts
src/TokenProviders/PortalTokenProvider.ts src/TokenProviders/PortalTokenProvider.ts
src/TokenProviders/TokenProviderFactory.ts src/TokenProviders/TokenProviderFactory.ts
src/Utils/DatabaseAccountUtils.test.ts
src/Utils/DatabaseAccountUtils.ts
src/Utils/PricingUtils.test.ts src/Utils/PricingUtils.test.ts
src/Utils/QueryUtils.test.ts src/Utils/QueryUtils.test.ts
src/Utils/QueryUtils.ts
src/applyExplorerBindings.ts src/applyExplorerBindings.ts
src/global.d.ts src/global.d.ts
src/setupTests.ts src/setupTests.ts

9
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"

940
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,9 +44,7 @@
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110", "@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7", "@uifabric/styling": "7.13.7",
"abort-controller": "3.0.0",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "file:./canvas", "canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19", "clean-webpack-plugin": "0.1.19",
@@ -61,8 +59,6 @@
"date-fns": "1.29.0", "date-fns": "1.29.0",
"dayjs": "1.8.19", "dayjs": "1.8.19",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"es6-object-assign": "1.1.0",
"es6-symbol": "3.1.3",
"eslint-plugin-jest": "23.13.2", "eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0", "eslint-plugin-react": "7.20.0",
"hasher": "1.2.0", "hasher": "1.2.0",
@@ -80,12 +76,9 @@
"monaco-editor": "0.18.1", "monaco-editor": "0.18.1",
"ms": "2.1.3", "ms": "2.1.3",
"msal": "1.4.4", "msal": "1.4.4",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.164.2", "office-ui-fabric-react": "7.164.2",
"p-retry": "4.2.0", "p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0",
"q": "1.5.1", "q": "1.5.1",
"react": "16.13.1", "react": "16.13.1",
"react-animate-height": "2.0.8", "react-animate-height": "2.0.8",
@@ -102,13 +95,9 @@
"rxjs": "6.6.3", "rxjs": "6.6.3",
"styled-components": "4.3.2", "styled-components": "4.3.2",
"swr": "0.4.0", "swr": "0.4.0",
"text-encoding": "0.7.0", "terser-webpack-plugin": "3.1.0",
"underscore": "1.9.1", "underscore": "1.9.1",
"url-polyfill": "1.1.7", "utility-types": "3.10.0"
"utility-types": "3.10.0",
"webcrypto-liner": "1.1.4",
"webfontloader": "1.6.28",
"whatwg-fetch": "3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.9.0", "@babel/core": "7.9.0",
@@ -138,9 +127,7 @@
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",
"@types/text-encoding": "0.0.33",
"@types/underscore": "1.7.36", "@types/underscore": "1.7.36",
"@types/webfontloader": "1.6.29",
"@typescript-eslint/eslint-plugin": "4.0.1", "@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1", "@typescript-eslint/parser": "4.0.1",
"axe-puppeteer": "1.1.0", "axe-puppeteer": "1.1.0",
@@ -165,7 +152,6 @@
"html-loader": "0.5.5", "html-loader": "0.5.5",
"html-loader-jest": "0.2.1", "html-loader-jest": "0.2.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"inline-css": "2.2.5",
"jest": "25.5.4", "jest": "25.5.4",
"jest-canvas-mock": "2.1.0", "jest-canvas-mock": "2.1.0",
"jest-puppeteer": "4.4.0", "jest-puppeteer": "4.4.0",
@@ -182,7 +168,6 @@
"rimraf": "3.0.0", "rimraf": "3.0.0",
"sinon": "3.2.1", "sinon": "3.2.1",
"style-loader": "0.23.0", "style-loader": "0.23.0",
"terser-webpack-plugin": "3.0.5",
"ts-loader": "6.2.2", "ts-loader": "6.2.2",
"tslint": "5.11.0", "tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0", "tslint-microsoft-contrib": "6.0.0",

View File

@@ -6,7 +6,7 @@ import Explorer from "../Explorer/Explorer";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab"; import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils"; import * as QueryUtils from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage"; import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";

View File

@@ -32,7 +32,6 @@ exports[`SettingsComponent renders 1`] = `
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isSystemDatabasePredicate": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -974,7 +973,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"nonSystemDatabases": [Function],
"notebookBasePath": [Function], "notebookBasePath": [Function],
"notebookServerInfo": [Function], "notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
@@ -1228,7 +1226,6 @@ exports[`SettingsComponent renders 1`] = `
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isSystemDatabasePredicate": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -2170,7 +2167,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"nonSystemDatabases": [Function],
"notebookBasePath": [Function], "notebookBasePath": [Function],
"notebookServerInfo": [Function], "notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
@@ -2437,7 +2433,6 @@ exports[`SettingsComponent renders 1`] = `
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isSystemDatabasePredicate": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -3379,7 +3374,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"nonSystemDatabases": [Function],
"notebookBasePath": [Function], "notebookBasePath": [Function],
"notebookServerInfo": [Function], "notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
@@ -3633,7 +3627,6 @@ exports[`SettingsComponent renders 1`] = `
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isSystemDatabasePredicate": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -4575,7 +4568,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"nonSystemDatabases": [Function],
"notebookBasePath": [Function], "notebookBasePath": [Function],
"notebookServerInfo": [Function], "notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],

View File

@@ -1,7 +1,7 @@
import React from "react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react";
import { DescriptionType, NumberUiType, SmartUiInput } from "../../../SelfServe/SelfServeTypes";
import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent"; import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent";
import { NumberUiType, SmartUiInput, DescriptionType } from "../../../SelfServe/SelfServeTypes";
describe("SmartUiComponent", () => { describe("SmartUiComponent", () => {
const exampleData: SmartUiDescriptor = { const exampleData: SmartUiDescriptor = {
@@ -97,9 +97,9 @@ describe("SmartUiComponent", () => {
dataFieldName: "database", dataFieldName: "database",
type: "object", type: "object",
choices: [ choices: [
{ label: "Database 1", key: "db1" }, { labelTKey: "Database 1", key: "db1" },
{ label: "Database 2", key: "db2" }, { labelTKey: "Database 2", key: "db2" },
{ label: "Database 3", key: "db3" }, { labelTKey: "Database 3", key: "db3" },
], ],
defaultKey: "db2", defaultKey: "db2",
}, },

View File

@@ -334,7 +334,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
dropdownWidth="auto" dropdownWidth="auto"
options={choices.map((c) => ({ options={choices.map((c) => ({
key: c.key, key: c.key,
text: this.props.getTranslation(c.label), text: this.props.getTranslation(c.labelTKey),
}))} }))}
styles={{ styles={{
root: { width: 400 }, root: { width: 400 },

View File

@@ -2,17 +2,17 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
jest.mock("../../Common/dataAccess/createCollection"); jest.mock("../../Common/dataAccess/createCollection");
jest.mock("../../Common/dataAccess/createDocument"); jest.mock("../../Common/dataAccess/createDocument");
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { createDocument } from "../../Common/dataAccess/createDocument"; import { createDocument } from "../../Common/dataAccess/createDocument";
import Explorer from "../Explorer"; import * as ViewModels from "../../Contracts/ViewModels";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
import Explorer from "../Explorer";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
describe("ContainerSampleGenerator", () => { describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): Explorer => { const createExplorerStub = (database: ViewModels.Database): Explorer => {
const explorerStub = {} as Explorer; const explorerStub = {} as Explorer;
explorerStub.nonSystemDatabases = ko.computed(() => [database]); explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false); explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false); explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => false); explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => false);

View File

@@ -1,9 +1,9 @@
import { DataSamplesUtil } from "./DataSamplesUtil";
import * as sinon from "sinon";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as ko from "knockout"; import * as ko from "knockout";
import * as sinon from "sinon";
import { Collection, Database } from "../../Contracts/ViewModels";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { Database, Collection } from "../../Contracts/ViewModels"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { DataSamplesUtil } from "./DataSamplesUtil";
describe("DataSampleUtils", () => { describe("DataSampleUtils", () => {
const sampleCollectionId = "sampleCollectionId"; const sampleCollectionId = "sampleCollectionId";
@@ -16,7 +16,7 @@ describe("DataSampleUtils", () => {
collections: ko.observableArray<Collection>([collection]), collections: ko.observableArray<Collection>([collection]),
} as Database; } as Database;
const explorer = {} as Explorer; const explorer = {} as Explorer;
explorer.nonSystemDatabases = ko.computed(() => [database]); explorer.databases = ko.observableArray<Database>([database]);
explorer.showOkModalDialog = () => {}; explorer.showOkModalDialog = () => {};
const dataSamplesUtil = new DataSamplesUtil(explorer); const dataSamplesUtil = new DataSamplesUtil(explorer);

View File

@@ -1,8 +1,8 @@
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
export class DataSamplesUtil { export class DataSamplesUtil {
private static readonly DialogTitle = "Create Sample Container"; private static readonly DialogTitle = "Create Sample Container";
@@ -17,7 +17,7 @@ export class DataSamplesUtil {
const databaseName = generator.getDatabaseId(); const databaseName = generator.getDatabaseId();
const containerName = generator.getCollectionId(); const containerName = generator.getCollectionId();
if (this.hasContainer(databaseName, containerName, this.container.nonSystemDatabases())) { if (this.hasContainer(databaseName, containerName, this.container.databases())) {
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`; const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg); this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);

View File

@@ -180,7 +180,6 @@ export default class Explorer {
// Resource Tree // Resource Tree
public databases: ko.ObservableArray<ViewModels.Database>; public databases: ko.ObservableArray<ViewModels.Database>;
public nonSystemDatabases: ko.Computed<ViewModels.Database[]>;
public selectedDatabaseId: ko.Computed<string>; public selectedDatabaseId: ko.Computed<string>;
public selectedCollectionId: ko.Computed<string>; public selectedCollectionId: ko.Computed<string>;
public isLeftPaneExpanded: ko.Observable<boolean>; public isLeftPaneExpanded: ko.Observable<boolean>;
@@ -256,7 +255,6 @@ export default class Explorer {
public closeDialog: ExplorerParams["closeDialog"]; public closeDialog: ExplorerParams["closeDialog"];
private _panes: ContextualPaneBase[] = []; private _panes: ContextualPaneBase[] = [];
private _isSystemDatabasePredicate: (database: ViewModels.Database) => boolean = (database) => false;
private _isInitializingNotebooks: boolean; private _isInitializingNotebooks: boolean;
private notebookBasePath: ko.Observable<string>; private notebookBasePath: ko.Observable<string>;
private _arcadiaManager: ArcadiaResourceManager; private _arcadiaManager: ArcadiaResourceManager;
@@ -527,17 +525,6 @@ export default class Explorer {
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph() configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
); );
this.isRightPanelV2Enabled = ko.computed<boolean>(() => userContext.features.enableRightPanelV2); this.isRightPanelV2Enabled = ko.computed<boolean>(() => userContext.features.enableRightPanelV2);
this.defaultExperience.subscribe((defaultExperience: string) => {
if (
defaultExperience &&
defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Cassandra.toLowerCase()
) {
this._isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return database.id() === "system";
};
}
});
this.selectedDatabaseId = ko.computed<string>(() => { this.selectedDatabaseId = ko.computed<string>(() => {
const selectedNode = this.selectedNode(); const selectedNode = this.selectedNode();
if (!selectedNode) { if (!selectedNode) {
@@ -559,10 +546,6 @@ export default class Explorer {
} }
}); });
this.nonSystemDatabases = ko.computed(() => {
return this.databases().filter((database: ViewModels.Database) => !this._isSystemDatabasePredicate(database));
});
this.addCollectionPane = new AddCollectionPane({ this.addCollectionPane = new AddCollectionPane({
isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()), isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()),
id: "addcollectionpane", id: "addcollectionpane",

View File

@@ -4,11 +4,8 @@
* - inspired from gremlin-javascript for nodejs: https://github.com/jbmusso/gremlin-javascript * - inspired from gremlin-javascript for nodejs: https://github.com/jbmusso/gremlin-javascript
* - tested on cosmosdb gremlin server * - tested on cosmosdb gremlin server
* - only supports sessionless gremlin requests * - only supports sessionless gremlin requests
* - Relies on text-encoding polyfill (github.com/inexorabletash/text-encoding) for TextEncoder/TextDecoder on IE, Edge.
*/ */
import { TextEncoder, TextDecoder } from "text-encoding";
export interface GremlinSimpleClientParameters { export interface GremlinSimpleClientParameters {
endpoint: string; // The websocket endpoint endpoint: string; // The websocket endpoint
user: string; user: string;

View File

@@ -114,7 +114,7 @@
aria-label="Keyspace id" aria-label="Keyspace id"
/> />
<datalist id="keyspacesList" data-bind="foreach: container.nonSystemDatabases"> <datalist id="keyspacesList" data-bind="foreach: container.databases">
<option data-bind="value: $data.id"></option> <option data-bind="value: $data.id"></option>
</datalist> </datalist>

View File

@@ -261,10 +261,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}); });
this.keyspaceIds(cachedKeyspaceIdsList); this.keyspaceIds(cachedKeyspaceIdsList);
}; };
this.container.nonSystemDatabases.subscribe((newDatabases: ViewModels.Database[]) => this.container.databases.subscribe((newDatabases: ViewModels.Database[]) => updateKeyspaceIds(newDatabases));
updateKeyspaceIds(newDatabases) updateKeyspaceIds(this.container.databases());
);
updateKeyspaceIds(this.container.nonSystemDatabases());
} }
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {

View File

@@ -11,7 +11,7 @@ import editable from "../../Common/EditableUtility";
import * as HeadersUtility from "../../Common/HeadersUtility"; import * as HeadersUtility from "../../Common/HeadersUtility";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import { DocumentsGridMetrics } from "../../Common/Constants"; import { DocumentsGridMetrics } from "../../Common/Constants";
import { QueryUtils } from "../../Utils/QueryUtils"; import * as QueryUtils from "../../Utils/QueryUtils";
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter"; import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import NewDocumentIcon from "../../../images/NewDocument.svg"; import NewDocumentIcon from "../../../images/NewDocument.svg";

View File

@@ -1,120 +0,0 @@
<div
class="tab-pane"
data-bind="
attr:{
id: tabId
}"
role="tabpanel"
>
<!-- Query Tab Command Bar - Start -->
<div class="contentdiv">
<div class="tabCommandButton">
<!-- Execute Query - Start -->
<span
class="commandButton"
data-bind="
click: onExecuteQueryClick,
visible: executeQueryButton.visible() && executeQueryButton.enabled()"
>
<img class="imgiconwidth" src="/ExecuteQuery.svg" />Run
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: executeQueryButton.visible() && !executeQueryButton.enabled()"
>
<img class="imgiconwidth" src="/ExecuteQuery-disabled.svg" />Run
</span>
<!-- Execute Query - End -->
</div>
</div>
<!-- Query Tab Command Bar - End -->
<div
class="queryEditor"
data-bind="
attr: {
id: queryEditorId
},
css: {
mongoQueryEditor:$root.isPreferredApiMongoDB()
}"
></div>
<div
style="margin-left: 50px; margin-top: -75px"
data-bind="
visible: $root.isPreferredApiMongoDB() && sqlQueryEditorContent().length == 0"
>
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
documents.
</div>
<!-- Query Errors Tab - Start-->
<div class="active queryErrorsHeaderContainer" data-bind="visible: errors().length > 0">
<span
class="queryErrors"
data-toggle="tab"
data-bind="
attr: {
href: '#queryerrors' + tabId
}"
>Errors</span
>
</div>
<!-- Query Errors Tab - End -->
<!-- Query Results & Errors Content Container - Start-->
<div class="queryResultErrorContentContainer">
<!-- Query Results Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryresults' + tabId
},
visible: allResultsMetadata().length > 0 && !errors().length > 0"
>
<div class="queryResultsValue">
<span class="queryResults"> Results: </span> <span data-bind="text: showingDocumentsDisplayText"></span>
<span class="queryResultDivider"> | </span> <span> Request Charge: </span>
<span data-bind="text: requestChargeDisplayText"></span> <span class="queryResultDivider"> | </span>
<span class="queryResultNextEnable" data-bind="visible: fetchNextPageButton.enabled">
<a
data-bind="
click: onFetchNextPageClick"
>
<span>Next</span> <img class="queryResultnextImg" src="/Query-Editor-Next.svg" />
</a>
</span>
<span class="queryResultNextDisable" data-bind="visible: !fetchNextPageButton.enabled()">
<span>Next</span> <img class="queryResultnextImg" src="/Query-Editor-Next-Disabled.svg" />
</span>
</div>
<div
style="height: 600px"
data-bind="
attr: {
id: resultsEditorId
}"
></div>
</div>
<!-- Query Results Content - Start-->
<!-- Query Errors Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryerrors' + tabId
},
visible: errors().length > 0"
>
<!-- ko foreach: errors -->
<div style="margin-left: 17px; font-size: 12px">
<span data-bind="text: $data.code"></span> : <span data-bind="text: $data.message"></span>
</div>
<!-- /ko -->
</div>
<!-- Query Errors Content - End-->
</div>
<!-- Results & Errors Content Container - Endt-->
</div>

View File

@@ -5,10 +5,8 @@ import QueryTab from "./QueryTab";
import * as HeadersUtility from "../../Common/HeadersUtility"; import * as HeadersUtility from "../../Common/HeadersUtility";
import { queryIterator } from "../../Common/MongoProxyClient"; import { queryIterator } from "../../Common/MongoProxyClient";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities"; import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
import template from "./MongoQueryTab.html";
export default class MongoQueryTab extends QueryTab { export default class MongoQueryTab extends QueryTab {
public static readonly component = { name: "mongo-query-tab", template };
public collection: ViewModels.Collection; public collection: ViewModels.Collection;
constructor(options: ViewModels.QueryTabOptions) { constructor(options: ViewModels.QueryTabOptions) {

View File

@@ -9,7 +9,7 @@ import * as HeadersUtility from "../../Common/HeadersUtility";
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter"; import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg"; import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import { QueryUtils } from "../../Utils/QueryUtils"; import * as QueryUtils from "../../Utils/QueryUtils";
import SaveQueryIcon from "../../../images/save-cosmos.svg"; import SaveQueryIcon from "../../../images/save-cosmos.svg";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities"; import { MinimalQueryIterator } from "../../Common/IteratorUtilities";

View File

@@ -76,10 +76,10 @@
<!-- Tabs Panes -- Start --> <!-- Tabs Panes -- Start -->
<div class="tabPanesContainer"> <div class="tabPanesContainer">
<!-- ko if: activeTab && activeTab() --> <!-- ko foreach: openedTabs -->
<div class="tabs-container" data-bind="visible: activeTab().isActive"> <div class="tabs-container" data-bind="visible: $data.isActive">
<span <span
data-bind="class: activeTab().constructor.component.name, component: { name: activeTab().constructor.component.name, params: activeTab }" data-bind="class: $data.constructor.component.name, component: { name: $data.constructor.component.name, params: $data }"
></span> ></span>
</div> </div>
<!-- /ko --> <!-- /ko -->

View File

@@ -1,8 +1,8 @@
import Explorer from "../Explorer";
import * as ko from "knockout"; import * as ko from "knockout";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
describe("ResourceTreeAdapter", () => { describe("ResourceTreeAdapter", () => {
const mockContainer = (): Explorer => const mockContainer = (): Explorer =>
@@ -18,7 +18,7 @@ describe("ResourceTreeAdapter", () => {
} as TabsBase), } as TabsBase),
}, },
isNotebookEnabled: ko.observable<boolean>(true), isNotebookEnabled: ko.observable<boolean>(true),
nonSystemDatabases: ko.observable<ViewModels.Database[]>([]), databases: ko.observable<ViewModels.Database[]>([]),
} as unknown) as Explorer); } as unknown) as Explorer);
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths // TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths

View File

@@ -1,39 +1,38 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { TreeComponent, TreeNode, TreeNodeMenuItem, TreeNodeComponent } from "../Controls/TreeComponent/TreeComponent";
import * as ViewModels from "../../Contracts/ViewModels";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
import DeleteIcon from "../../../images/delete.svg"; import DeleteIcon from "../../../images/delete.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg";
import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg";
import FileIcon from "../../../images/notebook/file-cosmos.svg";
import PublishIcon from "../../../images/notebook/publish_content.svg";
import { ArrayHashMap } from "../../Common/ArrayHashMap";
import { NotebookUtil } from "../Notebook/NotebookUtil";
import _ from "underscore";
import { IPinnedRepo } from "../../Juno/JunoClient";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../Common/Constants";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import GalleryIcon from "../../../images/GalleryIcon.svg"; import GalleryIcon from "../../../images/GalleryIcon.svg";
import { Callout, Text, Link, DirectionalHint, Stack, ICalloutProps, ILinkProps } from "office-ui-fabric-react"; import FileIcon from "../../../images/notebook/file-cosmos.svg";
import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import PublishIcon from "../../../images/notebook/publish_content.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { ArrayHashMap } from "../../Common/ArrayHashMap";
import { Areas } from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { IPinnedRepo } from "../../Juno/JunoClient";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import UserDefinedFunction from "./UserDefinedFunction"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil";
import TabsBase from "../Tabs/TabsBase";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger"; import Trigger from "./Trigger";
import TabsBase from "../Tabs/TabsBase"; import UserDefinedFunction from "./UserDefinedFunction";
import { userContext } from "../../UserContext";
import * as DataModels from "../../Contracts/DataModels";
export class ResourceTreeAdapter implements ReactAdapter { export class ResourceTreeAdapter implements ReactAdapter {
public static readonly MyNotebooksTitle = "My Notebooks"; public static readonly MyNotebooksTitle = "My Notebooks";
@@ -64,7 +63,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.koSubsCollectionIdMap = new ArrayHashMap(); this.koSubsCollectionIdMap = new ArrayHashMap();
this.databaseCollectionIdMap = new ArrayHashMap(); this.databaseCollectionIdMap = new ArrayHashMap();
this.container.nonSystemDatabases.subscribe((databases: ViewModels.Database[]) => { this.container.databases.subscribe((databases: ViewModels.Database[]) => {
// Clean up old databases // Clean up old databases
this.cleanupDatabasesKoSubs(); this.cleanupDatabasesKoSubs();
@@ -72,7 +71,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.triggerRender(); this.triggerRender();
}); });
this.container.nonSystemDatabases().forEach((database: ViewModels.Database) => this.watchDatabase(database)); this.container.databases().forEach((database: ViewModels.Database) => this.watchDatabase(database));
this.triggerRender(); this.triggerRender();
} }
@@ -190,7 +189,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
private buildDataTree(): TreeNode { private buildDataTree(): TreeNode {
const databaseTreeNodes: TreeNode[] = this.container.nonSystemDatabases().map((database: ViewModels.Database) => { const databaseTreeNodes: TreeNode[] = this.container.databases().map((database: ViewModels.Database) => {
const databaseNode: TreeNode = { const databaseNode: TreeNode = {
label: database.id(), label: database.id(),
iconSrc: CosmosDBIcon, iconSrc: CosmosDBIcon,

View File

@@ -1,5 +0,0 @@
Number.isInteger =
Number.isInteger ||
function(value) {
return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
};

View File

@@ -0,0 +1,5 @@
{
"Save": "Save",
"Discard": "Discard",
"Refresh": "Refesh"
}

View File

@@ -0,0 +1,31 @@
{
"NorthCentralUS": "North Central US",
"WestUS": "West US",
"EastUS2": "East US 2",
"Current Region": "Current Region",
"RegionDropdownInfo": "More regions can be added in the future.",
"RegionsAndAccountNameValidationError": "Regions and account name should not be empty.",
"DbThroughputValidationError": "Please update throughput for database.",
"DescriptionLabel": "Description",
"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 Database Level Throughput",
"Database Throughput": "Database Throughput",
"UpdateInProgressMessage": "Data is being updated",
"UpdateCompletedMessageTitle": "Update succeeded",
"UpdateCompletedMessageText": "Data update completed.",
"SubmissionMessageSuccessTitle": "Update started",
"SubmissionMessageForNewRegionText": "Data update started. Region changed.",
"SubmissionMessageForSameRegionText": "Data update started. Region not changed.",
"SubmissionMessageErrorTitle": "Data update failed",
"SubmissionMessageErrorText": "Data update failed because of errors.",
"OnSaveFailureMessage": "Data save operation not currently permitted."
}

View File

@@ -0,0 +1,52 @@
{
"DedicatedGatewayDescription": "Provision a dedicated gateway cluster for your Azure Cosmos DB account. A dedicated gateway is compute that is a front-end to data in your Azure Cosmos DB account. Your dedicated gateway automatically includes the integrated cache, which can improve read performance.",
"DedicatedGateway": "Dedicated Gateway",
"Enable": "Enable",
"Disable": "Disable",
"LearnAboutDedicatedGateway": "Learn more about dedicated gateway.",
"DeprovisioningDetailsText": "Learn more about deprovisioning the dedicated gateway.",
"DedicatedGatewayPricing": "Learn more about dedicated gateway pricing.",
"SKUs": "SKUs",
"SKUsPlaceHolder": "Select SKUs",
"NumberOfInstances": "Number of instances",
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
"CreateMessage": "Dedicated gateway resource is being created.",
"CreateInitializeTitle": "Provisioning resource",
"CreateInitializeMessage": "Dedicated gateway resource will be provisioned.",
"CreateSuccessTitle": "Resource provisioned",
"CreateSuccesseMessage": "Dedicated gateway resource provisioned.",
"CreateFailureTitle": "Failed to provision resource",
"CreateFailureMessage": "Dedicated gateway resource provisioning failed.",
"UpdateMessage": "Dedicated gateway resource is being updated.",
"UpdateInitializeTitle": "Updating resource",
"UpdateInitializeMessage": "Dedicated gateway resource will be updated.",
"UpdateSuccessTitle": "Resource updated",
"UpdateSuccesseMessage": "Dedicated gateway resource updated.",
"UpdateFailureTitle": "Failed to update resource",
"UpdateFailureMessage": "Dedicated gateway resource updation failed.",
"DeleteMessage": "Dedicated gateway resource is being deleted.",
"DeleteInitializeTitle": "Deleting resource",
"DeleteInitializeMessage": "Dedicated gateway resource will be deleted.",
"DeleteSuccessTitle": "Resource deleted",
"DeleteSuccesseMessage": "Dedicated gateway resource deleted.",
"DeleteFailureTitle": "Failed to delete resource",
"DeleteFailureMessage": "Dedicated gateway resource deletion failed.",
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.",
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
"NoValue": "",
"SKUDetails": "SKU Details:",
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
"CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory",
"Cost": "Cost",
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
"ConnectionString": "Connection String",
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
"KeysBlade": "the keys blade",
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
}

View File

@@ -1,92 +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",
"Current Region": "Current Region",
"RegionDropdownInfo": "More regions can be added in the future.",
"RegionsAndAccountNameValidationError": "Regions and account name should not be empty.",
"DbThroughputValidationError": "Please update throughput for database.",
"DescriptionLabel": "Description",
"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 Database Level Throughput",
"Database Throughput": "Database Throughput",
"UpdateInProgressMessage": "Data is being updated",
"UpdateCompletedMessageTitle": "Update succeeded",
"UpdateCompletedMessageText": "Data update completed.",
"SubmissionMessageSuccessTitle": "Update started",
"SubmissionMessageForNewRegionText": "Data update started. Region changed.",
"SubmissionMessageForSameRegionText": "Data update started. Region not changed.",
"SubmissionMessageErrorTitle": "Data update failed",
"SubmissionMessageErrorText": "Data update failed because of errors.",
"OnSaveFailureMessage": "Data save operation not currently permitted."
},
"SqlX": {
"DedicatedGatewayDescription": "Provision a dedicated gateway cluster for your Azure Cosmos DB account. A dedicated gateway is compute that is a front-end to data in your Azure Cosmos DB account. Your dedicated gateway automatically includes the integrated cache, which can improve read performance. ",
"DedicatedGateway": "Dedicated Gateway",
"Enable": "Enable",
"Disable": "Disable",
"LearnAboutDedicatedGateway": "Learn more about dedicated gateway.",
"DeprovisioningDetailsText": "Learn more about deprovisioning the dedicated gateway.",
"DedicatedGatewayPricing": "Learn more about dedicated gateway pricing",
"SKUs": "SKUs",
"SKUsPlaceHolder": "Select SKUs",
"NumberOfInstances": "Number of instances",
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
"CreateMessage": "Dedicated gateway resource is being created.",
"CreateInitializeTitle": "Provisioning resource",
"CreateInitializeMessage": "Dedicated gateway resource will be provisioned.",
"CreateSuccessTitle": "Resource provisioned",
"CreateSuccesseMessage": "Dedicated gateway resource provisioned.",
"CreateFailureTitle": "Failed to provision resource",
"CreateFailureMessage": "Dedicated gateway resource provisioning failed.",
"UpdateMessage": "Dedicated gateway resource is being updated.",
"UpdateInitializeTitle": "Updating resource",
"UpdateInitializeMessage": "Dedicated gateway resource will be updated.",
"UpdateSuccessTitle": "Resource updated",
"UpdateSuccesseMessage": "Dedicated gateway resource updated.",
"UpdateFailureTitle": "Failed to update resource",
"UpdateFailureMessage": "Dedicated gateway resource updation failed.",
"DeleteMessage": "Dedicated gateway resource is being deleted.",
"DeleteInitializeTitle": "Deleting resource",
"DeleteInitializeMessage": "Dedicated gateway resource will be deleted.",
"DeleteSuccessTitle": "Resource deleted",
"DeleteSuccesseMessage": "Dedicated gateway resource deleted.",
"DeleteFailureTitle": "Failed to delete resource",
"DeleteFailureMessage": "Dedicated gateway resource deletion failed.",
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment",
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
"NoValue": "",
"SKUDetails": "SKU Details: ",
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
"CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory",
"Cost": "Cost",
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
"ConnectionString": "Connection String",
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
"KeysBlade": "the keys blade",
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
}
}
}

View File

@@ -1,18 +1,8 @@
// CSS Dependencies // CSS Dependencies
import "abort-controller/polyfill";
import "babel-polyfill";
import "bootstrap/dist/css/bootstrap.css"; import "bootstrap/dist/css/bootstrap.css";
import "es6-object-assign/auto";
import "es6-symbol/implement";
import "object.entries/auto";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import "promise-polyfill/src/polyfill";
import "promise.prototype.finally/auto";
import React, { useState } from "react"; import React, { useState } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import "url-polyfill/url-polyfill.min";
import "webcrypto-liner/build/webcrypto-liner.shim.min";
import "whatwg-fetch";
import "../externals/jquery-ui.min.css"; import "../externals/jquery-ui.min.css";
import "../externals/jquery-ui.min.js"; import "../externals/jquery-ui.min.js";
import "../externals/jquery-ui.structure.min.css"; import "../externals/jquery-ui.structure.min.css";
@@ -64,7 +54,6 @@ import { useConfig } from "./hooks/useConfig";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer"; import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { useSidePanel } from "./hooks/useSidePanel"; import { useSidePanel } from "./hooks/useSidePanel";
import { KOCommentEnd, KOCommentIfStart } from "./koComment"; import { KOCommentEnd, KOCommentIfStart } from "./koComment";
import "./Libs/is-integer-polyfill";
import "./Libs/jquery"; import "./Libs/jquery";
import "./Shared/appInsights"; import "./Shared/appInsights";
import { userContext } from "./UserContext"; import { userContext } from "./UserContext";

View File

@@ -23,11 +23,18 @@ export type Features = {
readonly ttl90Days: boolean; readonly ttl90Days: boolean;
}; };
export function extractFeatures(params?: URLSearchParams): Features { export function extractFeatures(given = new URLSearchParams()): Features {
params = params || new URLSearchParams(window.location.search);
const downcased = new URLSearchParams(); const downcased = new URLSearchParams();
params.forEach((value, key) => downcased.append(key.toLocaleLowerCase(), value)); const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value);
const get = (key: string) => downcased.get("feature." + key.toLocaleLowerCase()) ?? undefined; const get = (key: string) => downcased.get("feature." + key) ?? undefined;
try {
new URLSearchParams(window.parent.location.search).forEach(set);
} catch {
//
} finally {
given.forEach(set);
}
return { return {
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"), canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),

View File

@@ -23,9 +23,9 @@ import {
} from "./SelfServeExample.rp"; } from "./SelfServeExample.rp";
const regionDropdownItems: ChoiceItem[] = [ const regionDropdownItems: ChoiceItem[] = [
{ label: "North Central US", key: Regions.NorthCentralUS }, { labelTKey: "NorthCentralUS", key: Regions.NorthCentralUS },
{ label: "West US", key: Regions.WestUS }, { labelTKey: "WestUS", key: Regions.WestUS },
{ label: "East US 2", key: Regions.EastUS2 }, { labelTKey: "EastUS2", key: Regions.EastUS2 },
]; ];
const regionDropdownInfo: Info = { const regionDropdownInfo: Info = {

View File

@@ -2,10 +2,12 @@ import { Spinner, SpinnerSize } from "office-ui-fabric-react";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import * as React from "react"; import * as React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { withTranslation } from "react-i18next";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility"; import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import { sendReadyMessage } from "../Common/MessageHandler"; import { sendReadyMessage } from "../Common/MessageHandler";
import { configContext, updateConfigContext } from "../ConfigContext"; import { configContext, updateConfigContext } from "../ConfigContext";
import { SelfServeFrameInputs } from "../Contracts/ViewModels"; import { SelfServeFrameInputs } from "../Contracts/ViewModels";
import i18n from "../i18n";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import "./SelfServe.less"; import "./SelfServe.less";
@@ -14,14 +16,35 @@ import { SelfServeDescriptor } from "./SelfServeTypes";
import { SelfServeType } from "./SelfServeUtils"; import { SelfServeType } from "./SelfServeUtils";
initializeIcons(); initializeIcons();
const loadTranslationFile = async (className: string): Promise<void> => {
const language = i18n.languages[0];
const fileName = `${className}.json`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let translations: any;
try {
translations = await import(`../Localization/${language}/${fileName}`);
} catch (e) {
translations = await import(`../Localization/en/${fileName}`);
}
i18n.addResourceBundle(language, className, translations.default, true);
};
const loadTranslations = async (className: string): Promise<void> => {
await loadTranslationFile("Common");
await loadTranslationFile(className);
};
const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDescriptor> => { const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDescriptor> => {
switch (selfServeType) { switch (selfServeType) {
case SelfServeType.example: { case SelfServeType.example: {
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample"); const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
await loadTranslations(SelfServeExample.default.name);
return new SelfServeExample.default().toSelfServeDescriptor(); return new SelfServeExample.default().toSelfServeDescriptor();
} }
case SelfServeType.sqlx: { case SelfServeType.sqlx: {
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX"); const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
await loadTranslations(SqlX.default.name);
return new SqlX.default().toSelfServeDescriptor(); return new SqlX.default().toSelfServeDescriptor();
} }
default: default:
@@ -33,7 +56,8 @@ const renderComponent = (selfServeDescriptor: SelfServeDescriptor): JSX.Element
if (!selfServeDescriptor) { if (!selfServeDescriptor) {
return <h1>Invalid self serve type!</h1>; return <h1>Invalid self serve type!</h1>;
} }
return <SelfServeComponent descriptor={selfServeDescriptor} />; const SelfServeComponentTranslated = withTranslation()(SelfServeComponent);
return <SelfServeComponentTranslated descriptor={selfServeDescriptor} />;
}; };
const renderSpinner = (): JSX.Element => { const renderSpinner = (): JSX.Element => {

View File

@@ -1,5 +1,5 @@
import React from "react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react";
import { SelfServeComponent, SelfServeComponentState } from "./SelfServeComponent"; import { SelfServeComponent, SelfServeComponentState } from "./SelfServeComponent";
import { NumberUiType, OnSaveResult, SelfServeDescriptor, SmartUiInput } from "./SelfServeTypes"; import { NumberUiType, OnSaveResult, SelfServeDescriptor, SmartUiInput } from "./SelfServeTypes";
@@ -87,9 +87,9 @@ describe("SelfServeComponent", () => {
dataFieldName: "database", dataFieldName: "database",
type: "object", type: "object",
choices: [ choices: [
{ label: "Database 1", key: "db1" }, { labelTKey: "Database 1", key: "db1" },
{ label: "Database 2", key: "db2" }, { labelTKey: "Database 2", key: "db2" },
{ label: "Database 3", key: "db3" }, { labelTKey: "Database 3", key: "db3" },
], ],
defaultKey: "db2", defaultKey: "db2",
}, },
@@ -106,7 +106,9 @@ describe("SelfServeComponent", () => {
}; };
it("should render and honor save, discard, refresh actions", async () => { it("should render and honor save, discard, refresh actions", async () => {
const wrapper = shallow(<SelfServeComponent descriptor={exampleData} />); const wrapper = shallow(
<SelfServeComponent descriptor={exampleData} t={undefined} i18n={undefined} tReady={undefined} />
);
await new Promise((resolve) => setTimeout(resolve, 0)); await new Promise((resolve) => setTimeout(resolve, 0));
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@@ -158,7 +160,9 @@ describe("SelfServeComponent", () => {
}); });
it("getResolvedValue", async () => { it("getResolvedValue", async () => {
const wrapper = shallow(<SelfServeComponent descriptor={exampleData} />); const wrapper = shallow(
<SelfServeComponent descriptor={exampleData} t={undefined} i18n={undefined} tReady={undefined} />
);
await new Promise((resolve) => setTimeout(resolve, 0)); await new Promise((resolve) => setTimeout(resolve, 0));
const selfServeComponent = wrapper.instance() as SelfServeComponent; const selfServeComponent = wrapper.instance() as SelfServeComponent;
@@ -179,7 +183,9 @@ describe("SelfServeComponent", () => {
it("message bar and spinner snapshots", async () => { it("message bar and spinner snapshots", async () => {
const newDescriptor = { ...exampleData, onRefresh: onRefreshIsUpdatingMock }; const newDescriptor = { ...exampleData, onRefresh: onRefreshIsUpdatingMock };
let wrapper = shallow(<SelfServeComponent descriptor={newDescriptor} />); let wrapper = shallow(
<SelfServeComponent descriptor={newDescriptor} t={undefined} i18n={undefined} tReady={undefined} />
);
await new Promise((resolve) => setTimeout(resolve, 0)); await new Promise((resolve) => setTimeout(resolve, 0));
let selfServeComponent = wrapper.instance() as SelfServeComponent; let selfServeComponent = wrapper.instance() as SelfServeComponent;
selfServeComponent.onSaveButtonClick(); selfServeComponent.onSaveButtonClick();
@@ -187,7 +193,9 @@ describe("SelfServeComponent", () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
newDescriptor.onRefresh = onRefreshMock; newDescriptor.onRefresh = onRefreshMock;
wrapper = shallow(<SelfServeComponent descriptor={newDescriptor} />); wrapper = shallow(
<SelfServeComponent descriptor={newDescriptor} t={undefined} i18n={undefined} tReady={undefined} />
);
await new Promise((resolve) => setTimeout(resolve, 0)); await new Promise((resolve) => setTimeout(resolve, 0));
selfServeComponent = wrapper.instance() as SelfServeComponent; selfServeComponent = wrapper.instance() as SelfServeComponent;
selfServeComponent.onSaveButtonClick(); selfServeComponent.onSaveButtonClick();

View File

@@ -8,15 +8,15 @@ import {
Spinner, Spinner,
SpinnerSize, SpinnerSize,
Stack, Stack,
Text,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import promiseRetry, { AbortError } from "p-retry"; import promiseRetry, { AbortError } from "p-retry";
import React from "react"; import React from "react";
import { Translation } from "react-i18next"; import { WithTranslation } from "react-i18next";
import * as _ from "underscore"; import * as _ from "underscore";
import { sendMessage } from "../Common/MessageHandler"; import { sendMessage } from "../Common/MessageHandler";
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts"; import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent"; import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
import "../i18n";
import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles"; import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles";
import { import {
AnyDisplay, AnyDisplay,
@@ -57,7 +57,7 @@ interface PortalNotificationContent {
}; };
} }
export interface SelfServeComponentProps { export interface SelfServeComponentProps extends WithTranslation {
descriptor: SelfServeDescriptor; descriptor: SelfServeDescriptor;
} }
@@ -108,6 +108,9 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
this.retryIntervalInMs = SelfServeComponent.defaultRetryIntervalInMs; this.retryIntervalInMs = SelfServeComponent.defaultRetryIntervalInMs;
} }
this.retryOptions = { forever: true, maxTimeout: this.retryIntervalInMs, minTimeout: this.retryIntervalInMs }; this.retryOptions = { forever: true, maxTimeout: this.retryIntervalInMs, minTimeout: this.retryIntervalInMs };
// translation function passed to SelfServeComponent
this.translationFunction = this.props.t;
} }
private onError = (hasErrors: boolean): void => { private onError = (hasErrors: boolean): void => {
@@ -391,8 +394,8 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
return this.getTranslation(key, "Common"); return this.getTranslation(key, "Common");
}; };
private getTranslation = (messageKey: string, prefix = `${this.smartUiGeneratorClassName}`): string => { private getTranslation = (messageKey: string, namespace = `${this.smartUiGeneratorClassName}`): string => {
const translationKey = `${prefix}.${messageKey}`; const translationKey = `${namespace}:${messageKey}`;
const translation = this.translationFunction ? this.translationFunction(translationKey) : messageKey; const translation = this.translationFunction ? this.translationFunction(translationKey) : messageKey;
if (translation === translationKey) { if (translation === translationKey) {
return messageKey; return messageKey;
@@ -441,15 +444,12 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
public render(): JSX.Element { public render(): JSX.Element {
if (this.state.compileErrorMessage) { if (this.state.compileErrorMessage) {
return <MessageBar messageBarType={MessageBarType.error}>{this.state.compileErrorMessage}</MessageBar>;
}
return ( return (
<Translation> <MessageBar messageBarType={MessageBarType.error}>
{(translate) => { <Text>{this.state.compileErrorMessage}</Text>
if (!this.translationFunction) { </MessageBar>
this.translationFunction = translate; );
} }
return ( return (
<div style={{ overflowX: "auto" }}> <div style={{ overflowX: "auto" }}>
<Stack tokens={containerStackTokens}> <Stack tokens={containerStackTokens}>
@@ -465,12 +465,10 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
<MessageBar <MessageBar
messageBarType={this.state.notification.type} messageBarType={this.state.notification.type}
onDismiss={ onDismiss={
this.state.notification.isCancellable this.state.notification.isCancellable ? () => this.setState({ notification: undefined }) : undefined
? () => this.setState({ notification: undefined })
: undefined
} }
> >
{this.state.notification.message} <Text>{this.state.notification.message}</Text>
</MessageBar> </MessageBar>
)} )}
<SmartUiComponent <SmartUiComponent
@@ -486,8 +484,5 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
</Stack> </Stack>
</div> </div>
); );
}}
</Translation>
);
} }
} }

View File

@@ -0,0 +1,69 @@
import { sendMessage } from "../Common/MessageHandler";
import { configContext } from "../ConfigContext";
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
import { appInsights } from "../Shared/appInsights";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import { userContext } from "../UserContext";
import { SelfServeTelemetryMessage } from "./SelfServeTypes";
const action = Action.SelfServe;
export const trace = (data: SelfServeTelemetryMessage): void => {
sendSelfServeTelemetryMessage(ActionModifiers.Mark, data);
appInsights.trackEvent({ name: Action[action] }, decorateData(data, ActionModifiers.Mark));
};
export const traceStart = (data: SelfServeTelemetryMessage): number => {
const timestamp: number = Date.now();
sendSelfServeTelemetryMessage(ActionModifiers.Start, data);
appInsights.startTrackEvent(Action[action]);
return timestamp;
};
export const traceSuccess = (data: SelfServeTelemetryMessage, timestamp?: number): void => {
sendSelfServeTelemetryMessage(ActionModifiers.Success, data, timestamp || Date.now());
appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Success));
};
export const traceFailure = (data: SelfServeTelemetryMessage, timestamp?: number): void => {
sendSelfServeTelemetryMessage(ActionModifiers.Failed, data, timestamp || Date.now());
appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Failed));
};
export const traceCancel = (data: SelfServeTelemetryMessage, timestamp?: number): void => {
sendSelfServeTelemetryMessage(ActionModifiers.Cancel, data, timestamp || Date.now());
appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Cancel));
};
const sendSelfServeTelemetryMessage = (
actionModifier: string,
data: SelfServeTelemetryMessage,
timeStamp?: number
): void => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dataToSend: any = {
type: SelfServeMessageTypes.TelemetryInfo,
data: {
action: Action[action],
actionModifier: actionModifier,
data: JSON.stringify(decorateData(data)),
},
};
if (timeStamp) {
dataToSend.data.timeStamp = timeStamp;
}
sendMessage(dataToSend);
};
const decorateData = (data: SelfServeTelemetryMessage, actionModifier?: string) => {
return {
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
authType: userContext.authType,
subscriptionId: userContext.subscriptionId,
platform: configContext.platform,
env: process.env.NODE_ENV,
actionModifier,
...data,
} as { [key: string]: string };
};

View File

@@ -98,7 +98,7 @@ export enum NumberUiType {
Slider = "Slider", Slider = "Slider",
} }
export type ChoiceItem = { label: string; key: string }; export type ChoiceItem = { labelTKey: string; key: string };
export type InputType = number | string | boolean | ChoiceItem | Description; export type InputType = number | string | boolean | ChoiceItem | Description;
@@ -157,3 +157,9 @@ export interface RefreshResult {
export interface RefreshParams { export interface RefreshParams {
retryIntervalInMs: number; retryIntervalInMs: number;
} }
export interface SelfServeTelemetryMessage {
selfServeClassName: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: any;
}

View File

@@ -131,9 +131,9 @@ describe("SelfServeUtils", () => {
type: "object", type: "object",
labelTKey: "Regions", labelTKey: "Regions",
choices: [ choices: [
{ label: "South West US", key: "SWUS" }, { labelTKey: "South West US", key: "SWUS" },
{ label: "North Central US", key: "NCUS" }, { labelTKey: "North Central US", key: "NCUS" },
{ label: "East US 2", key: "EUS2" }, { labelTKey: "East US 2", key: "EUS2" },
], ],
}, },
], ],
@@ -238,9 +238,9 @@ describe("SelfServeUtils", () => {
type: "object", type: "object",
labelTKey: "Regions", labelTKey: "Regions",
choices: [ choices: [
{ label: "South West US", key: "SWUS" }, { labelTKey: "South West US", key: "SWUS" },
{ label: "North Central US", key: "NCUS" }, { labelTKey: "North Central US", key: "NCUS" },
{ label: "East US 2", key: "EUS2" }, { labelTKey: "East US 2", key: "EUS2" },
], ],
}, },
children: [] as Node[], children: [] as Node[],

View File

@@ -195,5 +195,5 @@ export const generateBladeLink = (blade: BladeType): string => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroupName = userContext.resourceGroup; const resourceGroupName = userContext.resourceGroup;
const databaseAccountName = userContext.databaseAccount.name; const databaseAccountName = userContext.databaseAccount.name;
return `https://portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}/${blade}`; return `${document.referrer}#@microsoft.onmicrosoft.com/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}/${blade}`;
}; };

View File

@@ -1,4 +1,5 @@
import { IsDisplayable, OnChange, RefreshOptions, Values } from "../Decorators"; import { IsDisplayable, OnChange, RefreshOptions, Values } from "../Decorators";
import { trace } from "../SelfServeTelemetryProcessor";
import { import {
ChoiceItem, ChoiceItem,
Description, Description,
@@ -147,10 +148,10 @@ const onEnableDedicatedGatewayChange = (
}; };
const skuDropDownItems: ChoiceItem[] = [ const skuDropDownItems: ChoiceItem[] = [
{ label: "CosmosD4s", key: CosmosD4s }, { labelTKey: "CosmosD4s", key: CosmosD4s },
{ label: "CosmosD8s", key: CosmosD8s }, { labelTKey: "CosmosD8s", key: CosmosD8s },
{ label: "CosmosD16s", key: CosmosD16s }, { labelTKey: "CosmosD16s", key: CosmosD16s },
{ label: "CosmosD32s", key: CosmosD32s }, { labelTKey: "CosmosD32s", key: CosmosD32s },
]; ];
const getSkus = async (): Promise<ChoiceItem[]> => { const getSkus = async (): Promise<ChoiceItem[]> => {
@@ -176,6 +177,8 @@ export default class SqlX extends SelfServeBaseClass {
currentValues: Map<string, SmartUiInput>, currentValues: Map<string, SmartUiInput>,
baselineValues: Map<string, SmartUiInput> baselineValues: Map<string, SmartUiInput>
): Promise<OnSaveResult> => { ): Promise<OnSaveResult> => {
trace({ selfServeClassName: "SqlX" });
const dedicatedGatewayCurrentlyEnabled = currentValues.get("enableDedicatedGateway")?.value as boolean; const dedicatedGatewayCurrentlyEnabled = currentValues.get("enableDedicatedGateway")?.value as boolean;
const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean; const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean;

File diff suppressed because it is too large Load Diff

View File

@@ -114,6 +114,7 @@ export enum Action {
NotebooksGalleryPublicGalleryCount, NotebooksGalleryPublicGalleryCount,
NotebooksGalleryFavoritesCount, NotebooksGalleryFavoritesCount,
NotebooksGalleryPublishedCount, NotebooksGalleryPublishedCount,
SelfServe,
} }
export const ActionModifiers = { export const ActionModifiers = {

View File

@@ -1,58 +1,11 @@
import { Action, ActionModifiers } from "./TelemetryConstants"; import { sendMessage } from "../../Common/MessageHandler";
import { sendMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { appInsights } from "../appInsights";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { appInsights } from "../appInsights";
import { Action, ActionModifiers } from "./TelemetryConstants";
// TODO: Remove this. It is perfectly find to pass any data to telemtry methods. type TelemetryData = { [key: string]: unknown };
// This was added only to maintain stability while removing dependencies on explorer.databaseAccount and explorer.defaultExperience
type allowedKeys =
| "notebookId"
| "collectionId"
| "collectionName"
| "error"
| "isSample"
| "downloadCount"
| "baseUrl"
| "source"
| "description"
| "dataExplorerArea"
| "databaseName"
| "downloadCount"
| "favoriteCount"
| "abuseCategory"
| "errorStack"
| "tabTitle"
| "tabId"
| "conflictResourceType"
| "conflictOperationType"
| "conflictResourceId"
| "message"
| "files"
| "notebooks"
| "directories"
| "tabName"
| "databaseId"
| "queryName"
| "isPublishPending"
| "label"
| "scopesSelected"
| "title"
| "level"
| "changedSelectedValueTo"
| "area"
| "area"
| "paneTitle"
| "notebookUrl"
| "isNotebookEnabled"
| "commandButtonClicked"
| "count"
| "publishedCount"
| "underReviewCount"
| "removedCount";
type TelemetryData = { [key in allowedKeys]?: unknown };
export function trace(action: Action, actionModifier: string = ActionModifiers.Mark, data: TelemetryData = {}): void { export function trace(action: Action, actionModifier: string = ActionModifiers.Mark, data: TelemetryData = {}): void {
sendMessage({ sendMessage({

View File

@@ -1,5 +1,3 @@
import "babel-polyfill";
import "promise-polyfill/src/polyfill"; // polyfill Promise on IE
import "@jupyterlab/terminal/style/index.css"; import "@jupyterlab/terminal/style/index.css";
import "./index.css"; import "./index.css";
import { ServerConnection } from "@jupyterlab/services"; import { ServerConnection } from "@jupyterlab/services";

View File

@@ -2,7 +2,7 @@ import * as DataModels from "../Contracts/DataModels";
import * as Q from "q"; import * as Q from "q";
import * as sinon from "sinon"; import * as sinon from "sinon";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { QueryUtils } from "./QueryUtils"; import * as QueryUtils from "./QueryUtils";
describe("Query Utils", () => { describe("Query Utils", () => {
function generatePartitionKeyForPath(path: string): DataModels.PartitionKey { function generatePartitionKeyForPath(path: string): DataModels.PartitionKey {

View File

@@ -1,15 +1,13 @@
import Q from "q";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
export class QueryUtils { export function buildDocumentsQuery(
public static buildDocumentsQuery(
filter: string, filter: string,
partitionKeyProperty: string, partitionKeyProperty: string,
partitionKey: DataModels.PartitionKey partitionKey: DataModels.PartitionKey
): string { ): string {
let query: string = partitionKeyProperty let query = partitionKeyProperty
? `select c.id, c._self, c._rid, c._ts, ${QueryUtils.buildDocumentsQueryPartitionProjections( ? `select c.id, c._self, c._rid, c._ts, ${buildDocumentsQueryPartitionProjections(
"c", "c",
partitionKey partitionKey
)} as _partitionKeyValue from c` )} as _partitionKeyValue from c`
@@ -22,9 +20,9 @@ export class QueryUtils {
return query; return query;
} }
public static buildDocumentsQueryPartitionProjections( export function buildDocumentsQueryPartitionProjections(
collectionAlias: string, collectionAlias: string,
partitionKey: DataModels.PartitionKey partitionKey?: DataModels.PartitionKey
): string { ): string {
if (!partitionKey) { if (!partitionKey) {
return ""; return "";
@@ -32,23 +30,22 @@ export class QueryUtils {
// e.g., path /order/id will be projected as c["order"]["id"], // e.g., path /order/id will be projected as c["order"]["id"],
// to escape any property names that match a keyword // to escape any property names that match a keyword
let projections = []; const projections = [];
for (let index in partitionKey.paths) { for (const index in partitionKey.paths) {
// TODO: Handle "/" in partition key definitions // TODO: Handle "/" in partition key definitions
const projectedProperties: string[] = partitionKey.paths[index].split("/").slice(1); const projectedProperties: string[] = partitionKey.paths[index].split("/").slice(1);
let projectedProperty: string = ""; let projectedProperty = "";
projectedProperties.forEach((property: string) => { projectedProperties.forEach((property: string) => {
const projection = property.trim(); const projection = property.trim();
if (projection.length > 0 && projection.charAt(0) != "'" && projection.charAt(0) != '"') { if (projection.length > 0 && projection.charAt(0) !== "'" && projection.charAt(0) !== '"') {
projectedProperty = projectedProperty + `["${projection}"]`; projectedProperty += `["${projection}"]`;
} else if (projection.length > 0 && projection.charAt(0) == "'") { } else if (projection.length > 0 && projection.charAt(0) === "'") {
// trim single quotes and escape double quotes // trim single quotes and escape double quotes
const projectionSlice = projection.slice(1, projection.length - 1); const projectionSlice = projection.slice(1, projection.length - 1);
projectedProperty = projectedProperty += `["${projectionSlice.replace(/\\"/g, '"').replace(/"/g, '\\\\\\"')}"]`;
projectedProperty + `["${projectionSlice.replace(/\\"/g, '"').replace(/"/g, '\\\\\\"')}"]`;
} else { } else {
projectedProperty = projectedProperty + `[${projection}]`; projectedProperty += `[${projection}]`;
} }
}); });
@@ -58,19 +55,19 @@ export class QueryUtils {
return projections.join(","); return projections.join(",");
} }
public static async queryPagesUntilContentPresent( export const queryPagesUntilContentPresent = async (
firstItemIndex: number, firstItemIndex: number,
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults> queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
): Promise<ViewModels.QueryResults> { ): Promise<ViewModels.QueryResults> => {
let roundTrips: number = 0; let roundTrips = 0;
let netRequestCharge: number = 0; let netRequestCharge = 0;
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => { const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
const results: ViewModels.QueryResults = await queryItems(itemIndex); const results = await queryItems(itemIndex);
roundTrips = roundTrips + 1; roundTrips = roundTrips + 1;
results.roundTrips = roundTrips; results.roundTrips = roundTrips;
results.requestCharge = Number(results.requestCharge) + netRequestCharge; results.requestCharge = Number(results.requestCharge) + netRequestCharge;
netRequestCharge = Number(results.requestCharge); netRequestCharge = Number(results.requestCharge);
const resultsMetadata: ViewModels.QueryResultsMetadata = { const resultsMetadata = {
hasMoreResults: results.hasMoreResults, hasMoreResults: results.hasMoreResults,
itemCount: results.itemCount, itemCount: results.itemCount,
firstItemIndex: results.firstItemIndex, firstItemIndex: results.firstItemIndex,
@@ -83,11 +80,11 @@ export class QueryUtils {
}; };
return await doRequest(firstItemIndex); return await doRequest(firstItemIndex);
} };
public static async queryAllPages( export const queryAllPages = async (
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults> queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
): Promise<ViewModels.QueryResults> { ): Promise<ViewModels.QueryResults> => {
const queryResults: ViewModels.QueryResults = { const queryResults: ViewModels.QueryResults = {
documents: [], documents: [],
activityId: undefined, activityId: undefined,
@@ -99,7 +96,7 @@ export class QueryUtils {
roundTrips: 0, roundTrips: 0,
}; };
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => { const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
const results: ViewModels.QueryResults = await queryItems(itemIndex); const results = await queryItems(itemIndex);
const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results; const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results;
queryResults.roundTrips = queryResults.roundTrips + 1; queryResults.roundTrips = queryResults.roundTrips + 1;
queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge); queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge);
@@ -114,5 +111,4 @@ export class QueryUtils {
}; };
return doRequest(0); return doRequest(0);
} };
}

View File

@@ -260,7 +260,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
explorer.configure(inputs); explorer.configure(inputs);
resolve(explorer); resolve(explorer);
if (openAction) { if (openAction) {
handleOpenAction(openAction, explorer.nonSystemDatabases(), explorer); handleOpenAction(openAction, explorer.databases(), explorer);
} }
} }
}, },

View File

@@ -1,22 +1,14 @@
import i18n from "i18next"; import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import XHR from "i18next-http-backend";
import EnglishTranslations from "./Localization/en/translations.json";
i18n i18n
.use(XHR)
.use(LanguageDetector) .use(LanguageDetector)
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
resources: {
en: EnglishTranslations,
},
fallbackLng: "en", fallbackLng: "en",
detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] }, detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] },
debug: process.env.NODE_ENV === "development", debug: process.env.NODE_ENV === "development",
ns: ["translations"],
defaultNS: "translations",
keySeparator: ".", keySeparator: ".",
interpolation: { interpolation: {
formatSeparator: ",", formatSeparator: ",",
@@ -29,3 +21,5 @@ i18n
useSuspense: false, useSuspense: false,
}, },
}); });
export default i18n;

View File

@@ -1,7 +1,8 @@
import Adapter from "enzyme-adapter-react-16";
import { configure } from "enzyme"; import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import "jest-canvas-mock"; import "jest-canvas-mock";
import { initializeIcons } from "office-ui-fabric-react"; import { initializeIcons } from "office-ui-fabric-react";
import { TextDecoder, TextEncoder } from "util";
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });
initializeIcons(); initializeIcons();
@@ -13,3 +14,5 @@ if (typeof window.URL.createObjectURL === "undefined") {
(<any>window).$ = (<any>window).jQuery = require("jquery"); (<any>window).$ = (<any>window).jQuery = require("jquery");
(<any>global).$ = (<any>global).$.jQuery = require("jquery"); (<any>global).$ = (<any>global).$.jQuery = require("jquery");
require("jquery-ui-dist/jquery-ui"); require("jquery-ui-dist/jquery-ui");
(<any>global).TextEncoder = TextEncoder;
(<any>global).TextDecoder = TextDecoder;

View File

@@ -1,7 +1,6 @@
import "babel-polyfill";
import { DocumentClientParams, UploadDetailsRecord, UploadDetails } from "./definitions"; import { DocumentClientParams, UploadDetailsRecord, UploadDetails } from "./definitions";
import { client } from "../../Common/CosmosClient"; import { client } from "../../Common/CosmosClient";
import { configContext, updateConfigContext } from "../../ConfigContext"; import { updateConfigContext } from "../../ConfigContext";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils";

View File

@@ -11,7 +11,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"downlevelIteration": true, "downlevelIteration": true,
"module": "esnext", "module": "esnext",
"target": "es5", "target": "es2017",
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"lib": ["es5", "es6", "dom", "webworker.importscripts"], "lib": ["es5", "es6", "dom", "webworker.importscripts"],

View File

@@ -51,9 +51,9 @@
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.0", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
}, },
"supports-color": { "supports-color": {
"version": "7.1.0", "version": "7.1.0",

View File

@@ -12,6 +12,6 @@
"dependencies": { "dependencies": {
"chalk": "^4.1.0", "chalk": "^4.1.0",
"moment": "^2.27.0", "moment": "^2.27.0",
"node-fetch": "^2.6.0" "node-fetch": "^2.6.1"
} }
} }

View File

@@ -81,23 +81,6 @@ const typescriptRule = {
exclude: /node_modules/, exclude: /node_modules/,
}; };
// Third party modules are compiled with babel since using ts-loader that much causes webpack to run out of memory
const ModulesRule = {
test: /\.js$/,
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: ".cache/babel",
presets: [["@babel/preset-env", { targets: { ie: "11" }, useBuiltIns: false }]],
},
},
],
include: /node_modules/,
// Exclude large modules we know don't need transpiling
exclude: /vega|monaco|plotly/,
};
module.exports = function (env = {}, argv = {}) { module.exports = function (env = {}, argv = {}) {
const mode = argv.mode || "development"; const mode = argv.mode || "development";
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule]; const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
@@ -107,7 +90,6 @@ module.exports = function (env = {}, argv = {}) {
}; };
if (mode === "production") { if (mode === "production") {
rules.push(ModulesRule);
envVars.NODE_ENV = "production"; envVars.NODE_ENV = "production";
} }
@@ -235,7 +217,6 @@ module.exports = function (env = {}, argv = {}) {
minimize: mode === "production" ? true : false, minimize: mode === "production" ? true : false,
minimizer: [ minimizer: [
new TerserPlugin({ new TerserPlugin({
cache: ".cache/terser",
terserOptions: { terserOptions: {
// These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory // These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory
compress: false, compress: false,