mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 17:30:46 +00:00
resolve merge master conflict
This commit is contained in:
@@ -253,11 +253,8 @@ src/Terminal/NotebookAppContracts.d.ts
|
||||
src/Terminal/index.ts
|
||||
src/TokenProviders/PortalTokenProvider.ts
|
||||
src/TokenProviders/TokenProviderFactory.ts
|
||||
src/Utils/DatabaseAccountUtils.test.ts
|
||||
src/Utils/DatabaseAccountUtils.ts
|
||||
src/Utils/PricingUtils.test.ts
|
||||
src/Utils/QueryUtils.test.ts
|
||||
src/Utils/QueryUtils.ts
|
||||
src/applyExplorerBindings.ts
|
||||
src/global.d.ts
|
||||
src/setupTests.ts
|
||||
|
||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal 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
940
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -44,9 +44,7 @@
|
||||
"@types/node-fetch": "2.5.7",
|
||||
"@uifabric/react-cards": "0.109.110",
|
||||
"@uifabric/styling": "7.13.7",
|
||||
"abort-controller": "3.0.0",
|
||||
"applicationinsights": "1.8.0",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"bootstrap": "3.4.1",
|
||||
"canvas": "file:./canvas",
|
||||
"clean-webpack-plugin": "0.1.19",
|
||||
@@ -61,8 +59,6 @@
|
||||
"date-fns": "1.29.0",
|
||||
"dayjs": "1.8.19",
|
||||
"dotenv": "8.2.0",
|
||||
"es6-object-assign": "1.1.0",
|
||||
"es6-symbol": "3.1.3",
|
||||
"eslint-plugin-jest": "23.13.2",
|
||||
"eslint-plugin-react": "7.20.0",
|
||||
"hasher": "1.2.0",
|
||||
@@ -80,12 +76,9 @@
|
||||
"monaco-editor": "0.18.1",
|
||||
"ms": "2.1.3",
|
||||
"msal": "1.4.4",
|
||||
"object.entries": "1.1.0",
|
||||
"office-ui-fabric-react": "7.164.2",
|
||||
"p-retry": "4.2.0",
|
||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||
"promise-polyfill": "8.1.0",
|
||||
"promise.prototype.finally": "3.1.0",
|
||||
"q": "1.5.1",
|
||||
"react": "16.13.1",
|
||||
"react-animate-height": "2.0.8",
|
||||
@@ -102,13 +95,9 @@
|
||||
"rxjs": "6.6.3",
|
||||
"styled-components": "4.3.2",
|
||||
"swr": "0.4.0",
|
||||
"text-encoding": "0.7.0",
|
||||
"terser-webpack-plugin": "3.1.0",
|
||||
"underscore": "1.9.1",
|
||||
"url-polyfill": "1.1.7",
|
||||
"utility-types": "3.10.0",
|
||||
"webcrypto-liner": "1.1.4",
|
||||
"webfontloader": "1.6.28",
|
||||
"whatwg-fetch": "3.0.0"
|
||||
"utility-types": "3.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.9.0",
|
||||
@@ -138,9 +127,7 @@
|
||||
"@types/react-redux": "7.1.7",
|
||||
"@types/sinon": "2.3.3",
|
||||
"@types/styled-components": "5.1.1",
|
||||
"@types/text-encoding": "0.0.33",
|
||||
"@types/underscore": "1.7.36",
|
||||
"@types/webfontloader": "1.6.29",
|
||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||
"@typescript-eslint/parser": "4.0.1",
|
||||
"axe-puppeteer": "1.1.0",
|
||||
@@ -165,7 +152,6 @@
|
||||
"html-loader": "0.5.5",
|
||||
"html-loader-jest": "0.2.1",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"inline-css": "2.2.5",
|
||||
"jest": "25.5.4",
|
||||
"jest-canvas-mock": "2.1.0",
|
||||
"jest-puppeteer": "4.4.0",
|
||||
@@ -182,7 +168,6 @@
|
||||
"rimraf": "3.0.0",
|
||||
"sinon": "3.2.1",
|
||||
"style-loader": "0.23.0",
|
||||
"terser-webpack-plugin": "3.0.5",
|
||||
"ts-loader": "6.2.2",
|
||||
"tslint": "5.11.0",
|
||||
"tslint-microsoft-contrib": "6.0.0",
|
||||
|
||||
@@ -6,7 +6,7 @@ import Explorer from "../Explorer/Explorer";
|
||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import { QueryUtils } from "../Utils/QueryUtils";
|
||||
import * as QueryUtils from "../Utils/QueryUtils";
|
||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
||||
|
||||
@@ -32,7 +32,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_closeSynapseLinkModalDialog": [Function],
|
||||
"_isAfecFeatureRegistered": [Function],
|
||||
"_isInitializingNotebooks": false,
|
||||
"_isSystemDatabasePredicate": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
@@ -974,7 +973,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"nonSystemDatabases": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
@@ -1228,7 +1226,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_closeSynapseLinkModalDialog": [Function],
|
||||
"_isAfecFeatureRegistered": [Function],
|
||||
"_isInitializingNotebooks": false,
|
||||
"_isSystemDatabasePredicate": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
@@ -2170,7 +2167,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"nonSystemDatabases": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
@@ -2437,7 +2433,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_closeSynapseLinkModalDialog": [Function],
|
||||
"_isAfecFeatureRegistered": [Function],
|
||||
"_isInitializingNotebooks": false,
|
||||
"_isSystemDatabasePredicate": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
@@ -3379,7 +3374,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"nonSystemDatabases": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
@@ -3633,7 +3627,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_closeSynapseLinkModalDialog": [Function],
|
||||
"_isAfecFeatureRegistered": [Function],
|
||||
"_isInitializingNotebooks": false,
|
||||
"_isSystemDatabasePredicate": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
@@ -4575,7 +4568,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"nonSystemDatabases": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { DescriptionType, NumberUiType, SmartUiInput } from "../../../SelfServe/SelfServeTypes";
|
||||
import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent";
|
||||
import { NumberUiType, SmartUiInput, DescriptionType } from "../../../SelfServe/SelfServeTypes";
|
||||
|
||||
describe("SmartUiComponent", () => {
|
||||
const exampleData: SmartUiDescriptor = {
|
||||
@@ -97,9 +97,9 @@ describe("SmartUiComponent", () => {
|
||||
dataFieldName: "database",
|
||||
type: "object",
|
||||
choices: [
|
||||
{ label: "Database 1", key: "db1" },
|
||||
{ label: "Database 2", key: "db2" },
|
||||
{ label: "Database 3", key: "db3" },
|
||||
{ labelTKey: "Database 1", key: "db1" },
|
||||
{ labelTKey: "Database 2", key: "db2" },
|
||||
{ labelTKey: "Database 3", key: "db3" },
|
||||
],
|
||||
defaultKey: "db2",
|
||||
},
|
||||
|
||||
@@ -334,7 +334,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||
dropdownWidth="auto"
|
||||
options={choices.map((c) => ({
|
||||
key: c.key,
|
||||
text: this.props.getTranslation(c.label),
|
||||
text: this.props.getTranslation(c.labelTKey),
|
||||
}))}
|
||||
styles={{
|
||||
root: { width: 400 },
|
||||
|
||||
@@ -2,17 +2,17 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
||||
jest.mock("../../Common/dataAccess/createCollection");
|
||||
jest.mock("../../Common/dataAccess/createDocument");
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Q from "q";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||
import Explorer from "../Explorer";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
|
||||
describe("ContainerSampleGenerator", () => {
|
||||
const createExplorerStub = (database: ViewModels.Database): Explorer => {
|
||||
const explorerStub = {} as Explorer;
|
||||
explorerStub.nonSystemDatabases = ko.computed(() => [database]);
|
||||
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
|
||||
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false);
|
||||
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => false);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DataSamplesUtil } from "./DataSamplesUtil";
|
||||
import * as sinon from "sinon";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
import * as ko from "knockout";
|
||||
import * as sinon from "sinon";
|
||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
import { Database, Collection } from "../../Contracts/ViewModels";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
import { DataSamplesUtil } from "./DataSamplesUtil";
|
||||
|
||||
describe("DataSampleUtils", () => {
|
||||
const sampleCollectionId = "sampleCollectionId";
|
||||
@@ -16,7 +16,7 @@ describe("DataSampleUtils", () => {
|
||||
collections: ko.observableArray<Collection>([collection]),
|
||||
} as Database;
|
||||
const explorer = {} as Explorer;
|
||||
explorer.nonSystemDatabases = ko.computed(() => [database]);
|
||||
explorer.databases = ko.observableArray<Database>([database]);
|
||||
explorer.showOkModalDialog = () => {};
|
||||
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
|
||||
export class DataSamplesUtil {
|
||||
private static readonly DialogTitle = "Create Sample Container";
|
||||
@@ -17,7 +17,7 @@ export class DataSamplesUtil {
|
||||
|
||||
const databaseName = generator.getDatabaseId();
|
||||
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.`;
|
||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
|
||||
@@ -180,7 +180,6 @@ export default class Explorer {
|
||||
|
||||
// Resource Tree
|
||||
public databases: ko.ObservableArray<ViewModels.Database>;
|
||||
public nonSystemDatabases: ko.Computed<ViewModels.Database[]>;
|
||||
public selectedDatabaseId: ko.Computed<string>;
|
||||
public selectedCollectionId: ko.Computed<string>;
|
||||
public isLeftPaneExpanded: ko.Observable<boolean>;
|
||||
@@ -256,7 +255,6 @@ export default class Explorer {
|
||||
public closeDialog: ExplorerParams["closeDialog"];
|
||||
|
||||
private _panes: ContextualPaneBase[] = [];
|
||||
private _isSystemDatabasePredicate: (database: ViewModels.Database) => boolean = (database) => false;
|
||||
private _isInitializingNotebooks: boolean;
|
||||
private notebookBasePath: ko.Observable<string>;
|
||||
private _arcadiaManager: ArcadiaResourceManager;
|
||||
@@ -527,17 +525,6 @@ export default class Explorer {
|
||||
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
|
||||
);
|
||||
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>(() => {
|
||||
const selectedNode = this.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({
|
||||
isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()),
|
||||
id: "addcollectionpane",
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
* - inspired from gremlin-javascript for nodejs: https://github.com/jbmusso/gremlin-javascript
|
||||
* - tested on cosmosdb gremlin server
|
||||
* - 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 {
|
||||
endpoint: string; // The websocket endpoint
|
||||
user: string;
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
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>
|
||||
</datalist>
|
||||
|
||||
|
||||
@@ -261,10 +261,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
});
|
||||
this.keyspaceIds(cachedKeyspaceIdsList);
|
||||
};
|
||||
this.container.nonSystemDatabases.subscribe((newDatabases: ViewModels.Database[]) =>
|
||||
updateKeyspaceIds(newDatabases)
|
||||
);
|
||||
updateKeyspaceIds(this.container.nonSystemDatabases());
|
||||
this.container.databases.subscribe((newDatabases: ViewModels.Database[]) => updateKeyspaceIds(newDatabases));
|
||||
updateKeyspaceIds(this.container.databases());
|
||||
}
|
||||
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import editable from "../../Common/EditableUtility";
|
||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||
import TabsBase from "./TabsBase";
|
||||
import { DocumentsGridMetrics } from "../../Common/Constants";
|
||||
import { QueryUtils } from "../../Utils/QueryUtils";
|
||||
import * as QueryUtils from "../../Utils/QueryUtils";
|
||||
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import NewDocumentIcon from "../../../images/NewDocument.svg";
|
||||
|
||||
@@ -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>
|
||||
@@ -5,10 +5,8 @@ import QueryTab from "./QueryTab";
|
||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||
import { queryIterator } from "../../Common/MongoProxyClient";
|
||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||
import template from "./MongoQueryTab.html";
|
||||
|
||||
export default class MongoQueryTab extends QueryTab {
|
||||
public static readonly component = { name: "mongo-query-tab", template };
|
||||
public collection: ViewModels.Collection;
|
||||
|
||||
constructor(options: ViewModels.QueryTabOptions) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
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 { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||
|
||||
@@ -76,10 +76,10 @@
|
||||
|
||||
<!-- Tabs Panes -- Start -->
|
||||
<div class="tabPanesContainer">
|
||||
<!-- ko if: activeTab && activeTab() -->
|
||||
<div class="tabs-container" data-bind="visible: activeTab().isActive">
|
||||
<!-- ko foreach: openedTabs -->
|
||||
<div class="tabs-container" data-bind="visible: $data.isActive">
|
||||
<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>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Explorer from "../Explorer";
|
||||
import * as ko from "knockout";
|
||||
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
|
||||
|
||||
describe("ResourceTreeAdapter", () => {
|
||||
const mockContainer = (): Explorer =>
|
||||
@@ -18,7 +18,7 @@ describe("ResourceTreeAdapter", () => {
|
||||
} as TabsBase),
|
||||
},
|
||||
isNotebookEnabled: ko.observable<boolean>(true),
|
||||
nonSystemDatabases: ko.observable<ViewModels.Database[]>([]),
|
||||
databases: ko.observable<ViewModels.Database[]>([]),
|
||||
} as unknown) as Explorer);
|
||||
|
||||
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
import * as ko from "knockout";
|
||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "office-ui-fabric-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 CollectionIcon from "../../../images/tree-collection.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 { 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 { 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 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 Trigger from "./Trigger";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { userContext } from "../../UserContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import UserDefinedFunction from "./UserDefinedFunction";
|
||||
|
||||
export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public static readonly MyNotebooksTitle = "My Notebooks";
|
||||
@@ -64,7 +63,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.koSubsCollectionIdMap = new ArrayHashMap();
|
||||
this.databaseCollectionIdMap = new ArrayHashMap();
|
||||
|
||||
this.container.nonSystemDatabases.subscribe((databases: ViewModels.Database[]) => {
|
||||
this.container.databases.subscribe((databases: ViewModels.Database[]) => {
|
||||
// Clean up old databases
|
||||
this.cleanupDatabasesKoSubs();
|
||||
|
||||
@@ -72,7 +71,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.triggerRender();
|
||||
});
|
||||
|
||||
this.container.nonSystemDatabases().forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||
this.container.databases().forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
@@ -190,7 +189,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
}
|
||||
|
||||
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 = {
|
||||
label: database.id(),
|
||||
iconSrc: CosmosDBIcon,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
Number.isInteger =
|
||||
Number.isInteger ||
|
||||
function(value) {
|
||||
return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
|
||||
};
|
||||
5
src/Localization/en/Common.json
Normal file
5
src/Localization/en/Common.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Save": "Save",
|
||||
"Discard": "Discard",
|
||||
"Refresh": "Refesh"
|
||||
}
|
||||
31
src/Localization/en/SelfServeExample.json
Normal file
31
src/Localization/en/SelfServeExample.json
Normal 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."
|
||||
}
|
||||
52
src/Localization/en/SqlX.json
Normal file
52
src/Localization/en/SqlX.json
Normal 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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Main.tsx
11
src/Main.tsx
@@ -1,18 +1,8 @@
|
||||
// CSS Dependencies
|
||||
import "abort-controller/polyfill";
|
||||
import "babel-polyfill";
|
||||
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 "promise-polyfill/src/polyfill";
|
||||
import "promise.prototype.finally/auto";
|
||||
import React, { useState } from "react";
|
||||
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.js";
|
||||
import "../externals/jquery-ui.structure.min.css";
|
||||
@@ -64,7 +54,6 @@ import { useConfig } from "./hooks/useConfig";
|
||||
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
|
||||
import { useSidePanel } from "./hooks/useSidePanel";
|
||||
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
|
||||
import "./Libs/is-integer-polyfill";
|
||||
import "./Libs/jquery";
|
||||
import "./Shared/appInsights";
|
||||
import { userContext } from "./UserContext";
|
||||
|
||||
@@ -23,11 +23,18 @@ export type Features = {
|
||||
readonly ttl90Days: boolean;
|
||||
};
|
||||
|
||||
export function extractFeatures(params?: URLSearchParams): Features {
|
||||
params = params || new URLSearchParams(window.location.search);
|
||||
export function extractFeatures(given = new URLSearchParams()): Features {
|
||||
const downcased = new URLSearchParams();
|
||||
params.forEach((value, key) => downcased.append(key.toLocaleLowerCase(), value));
|
||||
const get = (key: string) => downcased.get("feature." + key.toLocaleLowerCase()) ?? undefined;
|
||||
const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value);
|
||||
const get = (key: string) => downcased.get("feature." + key) ?? undefined;
|
||||
|
||||
try {
|
||||
new URLSearchParams(window.parent.location.search).forEach(set);
|
||||
} catch {
|
||||
//
|
||||
} finally {
|
||||
given.forEach(set);
|
||||
}
|
||||
|
||||
return {
|
||||
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
||||
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
} from "./SelfServeExample.rp";
|
||||
|
||||
const regionDropdownItems: ChoiceItem[] = [
|
||||
{ label: "North Central US", key: Regions.NorthCentralUS },
|
||||
{ label: "West US", key: Regions.WestUS },
|
||||
{ label: "East US 2", key: Regions.EastUS2 },
|
||||
{ labelTKey: "NorthCentralUS", key: Regions.NorthCentralUS },
|
||||
{ labelTKey: "WestUS", key: Regions.WestUS },
|
||||
{ labelTKey: "EastUS2", key: Regions.EastUS2 },
|
||||
];
|
||||
|
||||
const regionDropdownInfo: Info = {
|
||||
|
||||
@@ -2,10 +2,12 @@ import { Spinner, SpinnerSize } from "office-ui-fabric-react";
|
||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||
import * as React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||
import { sendReadyMessage } from "../Common/MessageHandler";
|
||||
import { configContext, updateConfigContext } from "../ConfigContext";
|
||||
import { SelfServeFrameInputs } from "../Contracts/ViewModels";
|
||||
import i18n from "../i18n";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
||||
import "./SelfServe.less";
|
||||
@@ -14,14 +16,35 @@ import { SelfServeDescriptor } from "./SelfServeTypes";
|
||||
import { SelfServeType } from "./SelfServeUtils";
|
||||
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> => {
|
||||
switch (selfServeType) {
|
||||
case SelfServeType.example: {
|
||||
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
||||
await loadTranslations(SelfServeExample.default.name);
|
||||
return new SelfServeExample.default().toSelfServeDescriptor();
|
||||
}
|
||||
case SelfServeType.sqlx: {
|
||||
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
||||
await loadTranslations(SqlX.default.name);
|
||||
return new SqlX.default().toSelfServeDescriptor();
|
||||
}
|
||||
default:
|
||||
@@ -33,7 +56,8 @@ const renderComponent = (selfServeDescriptor: SelfServeDescriptor): JSX.Element
|
||||
if (!selfServeDescriptor) {
|
||||
return <h1>Invalid self serve type!</h1>;
|
||||
}
|
||||
return <SelfServeComponent descriptor={selfServeDescriptor} />;
|
||||
const SelfServeComponentTranslated = withTranslation()(SelfServeComponent);
|
||||
return <SelfServeComponentTranslated descriptor={selfServeDescriptor} />;
|
||||
};
|
||||
|
||||
const renderSpinner = (): JSX.Element => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { SelfServeComponent, SelfServeComponentState } from "./SelfServeComponent";
|
||||
import { NumberUiType, OnSaveResult, SelfServeDescriptor, SmartUiInput } from "./SelfServeTypes";
|
||||
|
||||
@@ -87,9 +87,9 @@ describe("SelfServeComponent", () => {
|
||||
dataFieldName: "database",
|
||||
type: "object",
|
||||
choices: [
|
||||
{ label: "Database 1", key: "db1" },
|
||||
{ label: "Database 2", key: "db2" },
|
||||
{ label: "Database 3", key: "db3" },
|
||||
{ labelTKey: "Database 1", key: "db1" },
|
||||
{ labelTKey: "Database 2", key: "db2" },
|
||||
{ labelTKey: "Database 3", key: "db3" },
|
||||
],
|
||||
defaultKey: "db2",
|
||||
},
|
||||
@@ -106,7 +106,9 @@ describe("SelfServeComponent", () => {
|
||||
};
|
||||
|
||||
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));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -158,7 +160,9 @@ describe("SelfServeComponent", () => {
|
||||
});
|
||||
|
||||
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));
|
||||
const selfServeComponent = wrapper.instance() as SelfServeComponent;
|
||||
|
||||
@@ -179,7 +183,9 @@ describe("SelfServeComponent", () => {
|
||||
|
||||
it("message bar and spinner snapshots", async () => {
|
||||
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));
|
||||
let selfServeComponent = wrapper.instance() as SelfServeComponent;
|
||||
selfServeComponent.onSaveButtonClick();
|
||||
@@ -187,7 +193,9 @@ describe("SelfServeComponent", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
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));
|
||||
selfServeComponent = wrapper.instance() as SelfServeComponent;
|
||||
selfServeComponent.onSaveButtonClick();
|
||||
|
||||
@@ -8,15 +8,15 @@ import {
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Stack,
|
||||
Text,
|
||||
} from "office-ui-fabric-react";
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import React from "react";
|
||||
import { Translation } from "react-i18next";
|
||||
import { WithTranslation } from "react-i18next";
|
||||
import * as _ from "underscore";
|
||||
import { sendMessage } from "../Common/MessageHandler";
|
||||
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
|
||||
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||
import "../i18n";
|
||||
import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles";
|
||||
import {
|
||||
AnyDisplay,
|
||||
@@ -57,7 +57,7 @@ interface PortalNotificationContent {
|
||||
};
|
||||
}
|
||||
|
||||
export interface SelfServeComponentProps {
|
||||
export interface SelfServeComponentProps extends WithTranslation {
|
||||
descriptor: SelfServeDescriptor;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,9 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
||||
this.retryIntervalInMs = SelfServeComponent.defaultRetryIntervalInMs;
|
||||
}
|
||||
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 => {
|
||||
@@ -391,8 +394,8 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
||||
return this.getTranslation(key, "Common");
|
||||
};
|
||||
|
||||
private getTranslation = (messageKey: string, prefix = `${this.smartUiGeneratorClassName}`): string => {
|
||||
const translationKey = `${prefix}.${messageKey}`;
|
||||
private getTranslation = (messageKey: string, namespace = `${this.smartUiGeneratorClassName}`): string => {
|
||||
const translationKey = `${namespace}:${messageKey}`;
|
||||
const translation = this.translationFunction ? this.translationFunction(translationKey) : messageKey;
|
||||
if (translation === translationKey) {
|
||||
return messageKey;
|
||||
@@ -441,53 +444,45 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (this.state.compileErrorMessage) {
|
||||
return <MessageBar messageBarType={MessageBarType.error}>{this.state.compileErrorMessage}</MessageBar>;
|
||||
return (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
<Text>{this.state.compileErrorMessage}</Text>
|
||||
</MessageBar>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Translation>
|
||||
{(translate) => {
|
||||
if (!this.translationFunction) {
|
||||
this.translationFunction = translate;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ overflowX: "auto" }}>
|
||||
<Stack tokens={containerStackTokens}>
|
||||
<Stack.Item>
|
||||
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
||||
<Separator styles={separatorStyles} />
|
||||
</Stack.Item>
|
||||
{this.state.isInitializing ? (
|
||||
<Spinner size={SpinnerSize.large} />
|
||||
) : (
|
||||
<>
|
||||
{this.state.notification && (
|
||||
<MessageBar
|
||||
messageBarType={this.state.notification.type}
|
||||
onDismiss={
|
||||
this.state.notification.isCancellable
|
||||
? () => this.setState({ notification: undefined })
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{this.state.notification.message}
|
||||
</MessageBar>
|
||||
)}
|
||||
<SmartUiComponent
|
||||
disabled={this.state.refreshResult?.isUpdateInProgress || this.state.isSaving}
|
||||
descriptor={this.state.root as SmartUiDescriptor}
|
||||
currentValues={this.state.currentValues}
|
||||
onInputChange={this.onInputChange}
|
||||
onError={this.onError}
|
||||
getTranslation={this.getTranslation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Translation>
|
||||
<div style={{ overflowX: "auto" }}>
|
||||
<Stack tokens={containerStackTokens}>
|
||||
<Stack.Item>
|
||||
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
||||
<Separator styles={separatorStyles} />
|
||||
</Stack.Item>
|
||||
{this.state.isInitializing ? (
|
||||
<Spinner size={SpinnerSize.large} />
|
||||
) : (
|
||||
<>
|
||||
{this.state.notification && (
|
||||
<MessageBar
|
||||
messageBarType={this.state.notification.type}
|
||||
onDismiss={
|
||||
this.state.notification.isCancellable ? () => this.setState({ notification: undefined }) : undefined
|
||||
}
|
||||
>
|
||||
<Text>{this.state.notification.message}</Text>
|
||||
</MessageBar>
|
||||
)}
|
||||
<SmartUiComponent
|
||||
disabled={this.state.refreshResult?.isUpdateInProgress || this.state.isSaving}
|
||||
descriptor={this.state.root as SmartUiDescriptor}
|
||||
currentValues={this.state.currentValues}
|
||||
onInputChange={this.onInputChange}
|
||||
onError={this.onError}
|
||||
getTranslation={this.getTranslation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
69
src/SelfServe/SelfServeTelemetryProcessor.ts
Normal file
69
src/SelfServe/SelfServeTelemetryProcessor.ts
Normal 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 };
|
||||
};
|
||||
@@ -98,7 +98,7 @@ export enum NumberUiType {
|
||||
Slider = "Slider",
|
||||
}
|
||||
|
||||
export type ChoiceItem = { label: string; key: string };
|
||||
export type ChoiceItem = { labelTKey: string; key: string };
|
||||
|
||||
export type InputType = number | string | boolean | ChoiceItem | Description;
|
||||
|
||||
@@ -157,3 +157,9 @@ export interface RefreshResult {
|
||||
export interface RefreshParams {
|
||||
retryIntervalInMs: number;
|
||||
}
|
||||
|
||||
export interface SelfServeTelemetryMessage {
|
||||
selfServeClassName: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?: any;
|
||||
}
|
||||
|
||||
@@ -131,9 +131,9 @@ describe("SelfServeUtils", () => {
|
||||
type: "object",
|
||||
labelTKey: "Regions",
|
||||
choices: [
|
||||
{ label: "South West US", key: "SWUS" },
|
||||
{ label: "North Central US", key: "NCUS" },
|
||||
{ label: "East US 2", key: "EUS2" },
|
||||
{ labelTKey: "South West US", key: "SWUS" },
|
||||
{ labelTKey: "North Central US", key: "NCUS" },
|
||||
{ labelTKey: "East US 2", key: "EUS2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -238,9 +238,9 @@ describe("SelfServeUtils", () => {
|
||||
type: "object",
|
||||
labelTKey: "Regions",
|
||||
choices: [
|
||||
{ label: "South West US", key: "SWUS" },
|
||||
{ label: "North Central US", key: "NCUS" },
|
||||
{ label: "East US 2", key: "EUS2" },
|
||||
{ labelTKey: "South West US", key: "SWUS" },
|
||||
{ labelTKey: "North Central US", key: "NCUS" },
|
||||
{ labelTKey: "East US 2", key: "EUS2" },
|
||||
],
|
||||
},
|
||||
children: [] as Node[],
|
||||
|
||||
@@ -195,5 +195,5 @@ export const generateBladeLink = (blade: BladeType): string => {
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroupName = userContext.resourceGroup;
|
||||
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}`;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsDisplayable, OnChange, RefreshOptions, Values } from "../Decorators";
|
||||
import { trace } from "../SelfServeTelemetryProcessor";
|
||||
import {
|
||||
ChoiceItem,
|
||||
Description,
|
||||
@@ -147,10 +148,10 @@ const onEnableDedicatedGatewayChange = (
|
||||
};
|
||||
|
||||
const skuDropDownItems: ChoiceItem[] = [
|
||||
{ label: "CosmosD4s", key: CosmosD4s },
|
||||
{ label: "CosmosD8s", key: CosmosD8s },
|
||||
{ label: "CosmosD16s", key: CosmosD16s },
|
||||
{ label: "CosmosD32s", key: CosmosD32s },
|
||||
{ labelTKey: "CosmosD4s", key: CosmosD4s },
|
||||
{ labelTKey: "CosmosD8s", key: CosmosD8s },
|
||||
{ labelTKey: "CosmosD16s", key: CosmosD16s },
|
||||
{ labelTKey: "CosmosD32s", key: CosmosD32s },
|
||||
];
|
||||
|
||||
const getSkus = async (): Promise<ChoiceItem[]> => {
|
||||
@@ -176,6 +177,8 @@ export default class SqlX extends SelfServeBaseClass {
|
||||
currentValues: Map<string, SmartUiInput>,
|
||||
baselineValues: Map<string, SmartUiInput>
|
||||
): Promise<OnSaveResult> => {
|
||||
trace({ selfServeClassName: "SqlX" });
|
||||
|
||||
const dedicatedGatewayCurrentlyEnabled = currentValues.get("enableDedicatedGateway")?.value as boolean;
|
||||
const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -114,6 +114,7 @@ export enum Action {
|
||||
NotebooksGalleryPublicGalleryCount,
|
||||
NotebooksGalleryFavoritesCount,
|
||||
NotebooksGalleryPublishedCount,
|
||||
SelfServe,
|
||||
}
|
||||
|
||||
export const ActionModifiers = {
|
||||
|
||||
@@ -1,58 +1,11 @@
|
||||
import { Action, ActionModifiers } from "./TelemetryConstants";
|
||||
import { sendMessage } from "../../Common/MessageHandler";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
import { appInsights } from "../appInsights";
|
||||
import { sendMessage } from "../../Common/MessageHandler";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
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.
|
||||
// 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 };
|
||||
type TelemetryData = { [key: string]: unknown };
|
||||
|
||||
export function trace(action: Action, actionModifier: string = ActionModifiers.Mark, data: TelemetryData = {}): void {
|
||||
sendMessage({
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "babel-polyfill";
|
||||
import "promise-polyfill/src/polyfill"; // polyfill Promise on IE
|
||||
import "@jupyterlab/terminal/style/index.css";
|
||||
import "./index.css";
|
||||
import { ServerConnection } from "@jupyterlab/services";
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as DataModels from "../Contracts/DataModels";
|
||||
import * as Q from "q";
|
||||
import * as sinon from "sinon";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { QueryUtils } from "./QueryUtils";
|
||||
import * as QueryUtils from "./QueryUtils";
|
||||
|
||||
describe("Query Utils", () => {
|
||||
function generatePartitionKeyForPath(path: string): DataModels.PartitionKey {
|
||||
|
||||
@@ -1,118 +1,114 @@
|
||||
import Q from "q";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
|
||||
export class QueryUtils {
|
||||
public static buildDocumentsQuery(
|
||||
filter: string,
|
||||
partitionKeyProperty: string,
|
||||
partitionKey: DataModels.PartitionKey
|
||||
): string {
|
||||
let query: string = partitionKeyProperty
|
||||
? `select c.id, c._self, c._rid, c._ts, ${QueryUtils.buildDocumentsQueryPartitionProjections(
|
||||
"c",
|
||||
partitionKey
|
||||
)} as _partitionKeyValue from c`
|
||||
: `select c.id, c._self, c._rid, c._ts from c`;
|
||||
export function buildDocumentsQuery(
|
||||
filter: string,
|
||||
partitionKeyProperty: string,
|
||||
partitionKey: DataModels.PartitionKey
|
||||
): string {
|
||||
let query = partitionKeyProperty
|
||||
? `select c.id, c._self, c._rid, c._ts, ${buildDocumentsQueryPartitionProjections(
|
||||
"c",
|
||||
partitionKey
|
||||
)} as _partitionKeyValue from c`
|
||||
: `select c.id, c._self, c._rid, c._ts from c`;
|
||||
|
||||
if (filter) {
|
||||
query += " " + filter;
|
||||
}
|
||||
|
||||
return query;
|
||||
if (filter) {
|
||||
query += " " + filter;
|
||||
}
|
||||
|
||||
public static buildDocumentsQueryPartitionProjections(
|
||||
collectionAlias: string,
|
||||
partitionKey: DataModels.PartitionKey
|
||||
): string {
|
||||
if (!partitionKey) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// e.g., path /order/id will be projected as c["order"]["id"],
|
||||
// to escape any property names that match a keyword
|
||||
let projections = [];
|
||||
for (let index in partitionKey.paths) {
|
||||
// TODO: Handle "/" in partition key definitions
|
||||
const projectedProperties: string[] = partitionKey.paths[index].split("/").slice(1);
|
||||
let projectedProperty: string = "";
|
||||
|
||||
projectedProperties.forEach((property: string) => {
|
||||
const projection = property.trim();
|
||||
if (projection.length > 0 && projection.charAt(0) != "'" && projection.charAt(0) != '"') {
|
||||
projectedProperty = projectedProperty + `["${projection}"]`;
|
||||
} else if (projection.length > 0 && projection.charAt(0) == "'") {
|
||||
// trim single quotes and escape double quotes
|
||||
const projectionSlice = projection.slice(1, projection.length - 1);
|
||||
projectedProperty =
|
||||
projectedProperty + `["${projectionSlice.replace(/\\"/g, '"').replace(/"/g, '\\\\\\"')}"]`;
|
||||
} else {
|
||||
projectedProperty = projectedProperty + `[${projection}]`;
|
||||
}
|
||||
});
|
||||
|
||||
projections.push(`${collectionAlias}${projectedProperty}`);
|
||||
}
|
||||
|
||||
return projections.join(",");
|
||||
}
|
||||
|
||||
public static async queryPagesUntilContentPresent(
|
||||
firstItemIndex: number,
|
||||
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
|
||||
): Promise<ViewModels.QueryResults> {
|
||||
let roundTrips: number = 0;
|
||||
let netRequestCharge: number = 0;
|
||||
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
|
||||
const results: ViewModels.QueryResults = await queryItems(itemIndex);
|
||||
roundTrips = roundTrips + 1;
|
||||
results.roundTrips = roundTrips;
|
||||
results.requestCharge = Number(results.requestCharge) + netRequestCharge;
|
||||
netRequestCharge = Number(results.requestCharge);
|
||||
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
||||
hasMoreResults: results.hasMoreResults,
|
||||
itemCount: results.itemCount,
|
||||
firstItemIndex: results.firstItemIndex,
|
||||
lastItemIndex: results.lastItemIndex,
|
||||
};
|
||||
if (resultsMetadata.itemCount === 0 && resultsMetadata.hasMoreResults) {
|
||||
return await doRequest(resultsMetadata.lastItemIndex);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
return await doRequest(firstItemIndex);
|
||||
}
|
||||
|
||||
public static async queryAllPages(
|
||||
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
|
||||
): Promise<ViewModels.QueryResults> {
|
||||
const queryResults: ViewModels.QueryResults = {
|
||||
documents: [],
|
||||
activityId: undefined,
|
||||
hasMoreResults: false,
|
||||
itemCount: 0,
|
||||
firstItemIndex: 0,
|
||||
lastItemIndex: 0,
|
||||
requestCharge: 0,
|
||||
roundTrips: 0,
|
||||
};
|
||||
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
|
||||
const results: ViewModels.QueryResults = await queryItems(itemIndex);
|
||||
const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results;
|
||||
queryResults.roundTrips = queryResults.roundTrips + 1;
|
||||
queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge);
|
||||
queryResults.hasMoreResults = hasMoreResults;
|
||||
queryResults.itemCount = queryResults.itemCount + itemCount;
|
||||
queryResults.lastItemIndex = lastItemIndex;
|
||||
queryResults.documents = queryResults.documents.concat(documents);
|
||||
if (queryResults.hasMoreResults) {
|
||||
return doRequest(queryResults.lastItemIndex + 1);
|
||||
}
|
||||
return queryResults;
|
||||
};
|
||||
|
||||
return doRequest(0);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
export function buildDocumentsQueryPartitionProjections(
|
||||
collectionAlias: string,
|
||||
partitionKey?: DataModels.PartitionKey
|
||||
): string {
|
||||
if (!partitionKey) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// e.g., path /order/id will be projected as c["order"]["id"],
|
||||
// to escape any property names that match a keyword
|
||||
const projections = [];
|
||||
for (const index in partitionKey.paths) {
|
||||
// TODO: Handle "/" in partition key definitions
|
||||
const projectedProperties: string[] = partitionKey.paths[index].split("/").slice(1);
|
||||
let projectedProperty = "";
|
||||
|
||||
projectedProperties.forEach((property: string) => {
|
||||
const projection = property.trim();
|
||||
if (projection.length > 0 && projection.charAt(0) !== "'" && projection.charAt(0) !== '"') {
|
||||
projectedProperty += `["${projection}"]`;
|
||||
} else if (projection.length > 0 && projection.charAt(0) === "'") {
|
||||
// trim single quotes and escape double quotes
|
||||
const projectionSlice = projection.slice(1, projection.length - 1);
|
||||
projectedProperty += `["${projectionSlice.replace(/\\"/g, '"').replace(/"/g, '\\\\\\"')}"]`;
|
||||
} else {
|
||||
projectedProperty += `[${projection}]`;
|
||||
}
|
||||
});
|
||||
|
||||
projections.push(`${collectionAlias}${projectedProperty}`);
|
||||
}
|
||||
|
||||
return projections.join(",");
|
||||
}
|
||||
|
||||
export const queryPagesUntilContentPresent = async (
|
||||
firstItemIndex: number,
|
||||
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
|
||||
): Promise<ViewModels.QueryResults> => {
|
||||
let roundTrips = 0;
|
||||
let netRequestCharge = 0;
|
||||
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
|
||||
const results = await queryItems(itemIndex);
|
||||
roundTrips = roundTrips + 1;
|
||||
results.roundTrips = roundTrips;
|
||||
results.requestCharge = Number(results.requestCharge) + netRequestCharge;
|
||||
netRequestCharge = Number(results.requestCharge);
|
||||
const resultsMetadata = {
|
||||
hasMoreResults: results.hasMoreResults,
|
||||
itemCount: results.itemCount,
|
||||
firstItemIndex: results.firstItemIndex,
|
||||
lastItemIndex: results.lastItemIndex,
|
||||
};
|
||||
if (resultsMetadata.itemCount === 0 && resultsMetadata.hasMoreResults) {
|
||||
return await doRequest(resultsMetadata.lastItemIndex);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
return await doRequest(firstItemIndex);
|
||||
};
|
||||
|
||||
export const queryAllPages = async (
|
||||
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
|
||||
): Promise<ViewModels.QueryResults> => {
|
||||
const queryResults: ViewModels.QueryResults = {
|
||||
documents: [],
|
||||
activityId: undefined,
|
||||
hasMoreResults: false,
|
||||
itemCount: 0,
|
||||
firstItemIndex: 0,
|
||||
lastItemIndex: 0,
|
||||
requestCharge: 0,
|
||||
roundTrips: 0,
|
||||
};
|
||||
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
|
||||
const results = await queryItems(itemIndex);
|
||||
const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results;
|
||||
queryResults.roundTrips = queryResults.roundTrips + 1;
|
||||
queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge);
|
||||
queryResults.hasMoreResults = hasMoreResults;
|
||||
queryResults.itemCount = queryResults.itemCount + itemCount;
|
||||
queryResults.lastItemIndex = lastItemIndex;
|
||||
queryResults.documents = queryResults.documents.concat(documents);
|
||||
if (queryResults.hasMoreResults) {
|
||||
return doRequest(queryResults.lastItemIndex + 1);
|
||||
}
|
||||
return queryResults;
|
||||
};
|
||||
|
||||
return doRequest(0);
|
||||
};
|
||||
|
||||
@@ -260,7 +260,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
|
||||
explorer.configure(inputs);
|
||||
resolve(explorer);
|
||||
if (openAction) {
|
||||
handleOpenAction(openAction, explorer.nonSystemDatabases(), explorer);
|
||||
handleOpenAction(openAction, explorer.databases(), explorer);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
10
src/i18n.ts
10
src/i18n.ts
@@ -1,22 +1,14 @@
|
||||
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: ",",
|
||||
@@ -29,3 +21,5 @@ i18n
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import Adapter from "enzyme-adapter-react-16";
|
||||
import { configure } from "enzyme";
|
||||
import Adapter from "enzyme-adapter-react-16";
|
||||
import "jest-canvas-mock";
|
||||
import { initializeIcons } from "office-ui-fabric-react";
|
||||
import { TextDecoder, TextEncoder } from "util";
|
||||
configure({ adapter: new Adapter() });
|
||||
initializeIcons();
|
||||
|
||||
@@ -13,3 +14,5 @@ if (typeof window.URL.createObjectURL === "undefined") {
|
||||
(<any>window).$ = (<any>window).jQuery = require("jquery");
|
||||
(<any>global).$ = (<any>global).$.jQuery = require("jquery");
|
||||
require("jquery-ui-dist/jquery-ui");
|
||||
(<any>global).TextEncoder = TextEncoder;
|
||||
(<any>global).TextDecoder = TextDecoder;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "babel-polyfill";
|
||||
import { DocumentClientParams, UploadDetailsRecord, UploadDetails } from "./definitions";
|
||||
import { client } from "../../Common/CosmosClient";
|
||||
import { configContext, updateConfigContext } from "../../ConfigContext";
|
||||
import { updateConfigContext } from "../../ConfigContext";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"target": "es2017",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"lib": ["es5", "es6", "dom", "webworker.importscripts"],
|
||||
|
||||
6
utils/deployment-status/package-lock.json
generated
6
utils/deployment-status/package-lock.json
generated
@@ -51,9 +51,9 @@
|
||||
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.1.0",
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.0",
|
||||
"moment": "^2.27.0",
|
||||
"node-fetch": "^2.6.0"
|
||||
"node-fetch": "^2.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,23 +81,6 @@ const typescriptRule = {
|
||||
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 = {}) {
|
||||
const mode = argv.mode || "development";
|
||||
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
|
||||
@@ -107,7 +90,6 @@ module.exports = function (env = {}, argv = {}) {
|
||||
};
|
||||
|
||||
if (mode === "production") {
|
||||
rules.push(ModulesRule);
|
||||
envVars.NODE_ENV = "production";
|
||||
}
|
||||
|
||||
@@ -235,7 +217,6 @@ module.exports = function (env = {}, argv = {}) {
|
||||
minimize: mode === "production" ? true : false,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
cache: ".cache/terser",
|
||||
terserOptions: {
|
||||
// These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory
|
||||
compress: false,
|
||||
|
||||
Reference in New Issue
Block a user