Compare commits

...

42 Commits

Author SHA1 Message Date
hardiknai-techm
f9780ff3ad Merge branch 'master' of https://github.com/Azure/cosmos-explorer into resolve_Constants 2021-10-12 21:02:28 +05:30
vaidankarswapnil
aff7133095 Fix eslint issues for TableCommands and other files (#1132) 2021-10-12 08:07:06 -07:00
Hardikkumar Nai
bfd4948fb9 absulte_path setting (#984)
* absulte_path setting

* resolve build time error
2021-10-12 07:38:34 -07:00
victor-meng
1c54459708 If unsharded is checked, set partition key to undefined (#1128) 2021-10-11 12:09:38 -07:00
Sunil Kumar Yadav
df3b18d585 fixed eslint of NotebookComponentBootstrapper and NotebookReadOnlyRenderer (#1122) 2021-10-11 08:29:21 -07:00
Sunil Kumar Yadav
882f0e1554 fixed GraphExplorer.tsx ellint issue (#1124) 2021-10-11 08:21:52 -07:00
vaidankarswapnil
b67b76cc87 Fix eslint issues for QueryBuilderViewModal and QueryClauseViewModel (#1125) 2021-10-11 08:20:38 -07:00
vaidankarswapnil
734ee1e436 Fix eslint issues for ClauseGroup and ClauseGroupViewModel files (#1127) 2021-10-11 07:56:12 -07:00
Sunil Kumar Yadav
ff498b51e2 fixed eslint of Trigger.ts GithubOAuthService.ts etc (#1126) 2021-10-11 07:55:21 -07:00
vaidankarswapnil
ed1ffb692f Fix ally database panel open issue (#1120) 2021-10-06 07:53:46 -07:00
Karthik chakravarthy
f7fa3f7c09 Fix Unit Test: Mock the class to its instance (#1117)
* mock to instance

* Update jest.config.js

Co-authored-by: Jordi Bunster <jbunster@microsoft.com>
2021-10-05 13:06:26 -07:00
Jordi Bunster
6ebf19c0c9 Close tab fixes (#1107)
* Close tab fixes

Ensure that when promoting a new tab to being the 'active' tab
(as a consequence of, say, closing the active tab) that the newly
promoted tab has a chance to install its buttons and what not.

* Set new active tab even if undefined
2021-10-05 09:25:35 -07:00
hardiknai-techm
deb18e5aa1 resolve master merge conflict 2021-10-01 19:34:54 +05:30
Karthik chakravarthy
f968f57543 Allocation of container on demand (#1097)
* Allocation of container only on demand

* git notebook click emits connect to container and refresh notebooks

* git cleanup local repo

* Reconnect rename and memory heartbeat change

* Fix new notebook click event in case of git login

* Mongo proxy test replace with master file

* code refactor

* format check

* compile errors resolved

* address review comments

* rename environment to workspace

* Added content html rendering in Dialog

* format issue

* Added contentHtml in the dialog which renders jsx element
2021-09-30 12:53:33 -04:00
hardiknai-techm
6da226fedb resolve master conflict 2021-09-30 19:48:19 +05:30
vaidankarswapnil
6ca8e3c6f4 Fix execute Query 'Results' and 'Query status' section controls gets truncated at 200% resize mode of 'Query1' blade (#1105)
* Fix a11y querytab results section zoom section issue

* Update test smapshot

* Update test snapshot

* Resolved test case issue
2021-09-27 11:03:01 -07:00
vaidankarswapnil
f7e7240010 Fix keyboard focus does not retain on 'New Database' button after closing the 'New Database' blade via ESC key (#1109)
* Fix a11y new database button focus issue

* Update test snapshot and other issues
2021-09-23 08:13:18 -07:00
Sunil Kumar Yadav
acc095a482 fixed graph input query JAWS screen reader issue (#1108) 2021-09-23 08:11:46 -07:00
Hardikkumar Nai
288e6410f3 Choose Column: Aria Position set is not defined for check box present under choose column dialog box. (#1104) 2021-09-23 07:59:10 -07:00
Hardikkumar Nai
9ac3392271 Scale and Settings: 'Learn more' link is not adapting the high contrast mode after applying high contrast theme. (#1103) 2021-09-23 07:58:41 -07:00
Sunil Kumar Yadav
9a908dde9a fixed graph explorer visibility and graph expand keyboard accessibility issue (#1092)
* fixed graph explorer visibility issue

* fixed graph expand keyboard accessibility issue
2021-09-23 07:57:42 -07:00
siddjoshi-ms
ddd2e63fe7 Telemetry added for calculate cost function (#1018)
* Added telemetry for sql cost calculation
2021-09-22 09:49:45 -07:00
Hardikkumar Nai
34c59b4872 Scale and Settings: Text content of 'Info status message' and 'Warning' message is not visible properly at high contrast black mode. (#1090) 2021-09-22 07:40:18 -07:00
Jordi Bunster
7d28af4fc7 Make these required fields (#1101) 2021-09-21 15:50:44 -07:00
Sunil Kumar Yadav
50b99ceb42 fixed horizontal scrollbar issue on 400% resize mode (#1099) 2021-09-21 09:06:49 -07:00
Sunil Kumar Yadav
15a26d6500 fixed notification content visible issue on 400 resize mode (#1098) 2021-09-21 09:06:17 -07:00
Sunil Kumar Yadav
a8150af269 fixed incorrect notification console expand collaped screenreader issue (#1095) 2021-09-21 09:04:47 -07:00
Sunil Kumar Yadav
6a9a0156a3 fixed entity choose column role accessibility issue (#1088) 2021-09-21 09:02:04 -07:00
vaidankarswapnil
ead28f043f Fix after activating "Refresh tree" button, 'Querying database' message appears but screen reader does not provide any information about it (#1091)
* Fix a11y refresh tree querying database msg

* Update test snapshot issue
2021-09-21 09:00:28 -07:00
vaidankarswapnil
b05e5a2145 Fix delete database warning message is not announced by the screen reader after selecting 'Delete Database' menu item (#1074)
* Fix a11y delete database confirmation ississue

* Resolved lint issue - Removed Unnecessary semicolon

* Resolved compilation issue for extractFeature.ts and update test snapshot issue

Co-authored-by: Armando Trejo Oliver <ato9000@users.noreply.github.com>
2021-09-21 08:58:03 -07:00
Hardikkumar Nai
5e8aa491ba Load Graph: Name, role and state properties are not defined for 'full screen graph view' control present under 'Graph' tab section. (#1083) 2021-09-21 08:57:08 -07:00
Hardikkumar Nai
a730c08292 New SQL Query: Luminosity contrast ratio is 2.6:1 which is less than 3:1 for Close(X) icon button of Query 1 tab control (#1089) 2021-09-21 08:56:16 -07:00
Hardikkumar Nai
3dce5cd129 Tooltip is not provided for 'More' control present on the page. (#1093) 2021-09-21 08:54:44 -07:00
siddjoshi-ms
7c186c3ef2 Update GraphAPICompute.rp.ts (#1065) 2021-09-20 15:13:07 -07:00
hardiknai-techm
d301294eb5 resolve build time error 2021-08-17 15:28:19 +05:30
hardiknai-techm
9fd50ee9dd resolve build time error 2021-08-17 15:26:41 +05:30
hardiknai-techm
413472045d remove file from constants 2021-08-17 09:02:37 +05:30
hardiknai-techm
989f7b9cd2 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into resolve_Constants 2021-08-17 08:26:56 +05:30
hardiknai-techm
00ccf4e525 resolve merge conflict 2021-08-14 11:19:28 +05:30
hardiknai-techm
afccd262a9 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into resolve_Constants 2021-08-11 19:49:48 +05:30
hardiknai-techm
e9ad009f79 added constants/index.ts file in ignore 2021-08-11 19:49:17 +05:30
hardiknai-techm
fc1067137b resolve esint error Constants 2021-08-06 20:07:01 +05:30
119 changed files with 2124 additions and 1631 deletions

View File

@@ -6,7 +6,7 @@ src/Api/Apis.ts
src/AuthType.ts src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts src/Bindings/BindingHandlersRegisterer.ts
src/Bindings/ReactBindingHandler.ts src/Bindings/ReactBindingHandler.ts
src/Common/Constants.ts src/Common/Constants/index.ts
src/Common/CosmosClient.test.ts src/Common/CosmosClient.test.ts
src/Common/CosmosClient.ts src/Common/CosmosClient.ts
src/Common/DataAccessUtilityBase.test.ts src/Common/DataAccessUtilityBase.test.ts
@@ -81,17 +81,13 @@ src/Explorer/Tables/DataTable/DataTableBindingManager.ts
src/Explorer/Tables/DataTable/DataTableBuilder.ts src/Explorer/Tables/DataTable/DataTableBuilder.ts
src/Explorer/Tables/DataTable/DataTableContextMenu.ts src/Explorer/Tables/DataTable/DataTableContextMenu.ts
src/Explorer/Tables/DataTable/DataTableOperationManager.ts src/Explorer/Tables/DataTable/DataTableOperationManager.ts
src/Explorer/Tables/DataTable/DataTableOperations.ts # src/Explorer/Tables/DataTable/DataTableOperations.ts
src/Explorer/Tables/DataTable/DataTableViewModel.ts src/Explorer/Tables/DataTable/DataTableViewModel.ts
src/Explorer/Tables/DataTable/TableCommands.ts # src/Explorer/Tables/DataTable/TableCommands.ts
src/Explorer/Tables/DataTable/TableEntityCache.ts # src/Explorer/Tables/DataTable/TableEntityCache.ts
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
src/Explorer/Tables/Entities.ts # src/Explorer/Tables/Entities.ts
src/Explorer/Tables/QueryBuilder/ClauseGroup.ts
src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
src/Explorer/Tables/TableDataClient.ts src/Explorer/Tables/TableDataClient.ts
src/Explorer/Tables/TableEntityProcessor.ts src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts src/Explorer/Tables/Utilities.ts
@@ -115,15 +111,10 @@ src/Explorer/Tree/ObjectId.ts
src/Explorer/Tree/ResourceTokenCollection.ts src/Explorer/Tree/ResourceTokenCollection.ts
src/Explorer/Tree/StoredProcedure.ts src/Explorer/Tree/StoredProcedure.ts
src/Explorer/Tree/TreeComponents.ts src/Explorer/Tree/TreeComponents.ts
src/Explorer/Tree/Trigger.ts
src/Explorer/WaitsForTemplateViewModel.ts src/Explorer/WaitsForTemplateViewModel.ts
src/GitHub/GitHubClient.test.ts src/GitHub/GitHubClient.test.ts
src/GitHub/GitHubClient.ts src/GitHub/GitHubClient.ts
src/GitHub/GitHubConnector.ts
src/GitHub/GitHubOAuthService.ts
src/Index.ts src/Index.ts
src/Juno/JunoClient.test.ts
src/Juno/JunoClient.ts
src/Platform/Hosted/Authorization.ts src/Platform/Hosted/Authorization.ts
src/ReactDevTools.ts src/ReactDevTools.ts
src/Shared/Constants.ts src/Shared/Constants.ts
@@ -143,7 +134,7 @@ src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx src/Explorer/Controls/TreeComponent/TreeComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx ; src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
@@ -153,10 +144,10 @@ src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.t
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx ; src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
src/Explorer/Notebook/NotebookComponent/contents/index.tsx src/Explorer/Notebook/NotebookComponent/contents/index.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx ; src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx

View File

@@ -39,7 +39,6 @@ module.exports = {
"@typescript-eslint/switch-exhaustiveness-check": "error", "@typescript-eslint/switch-exhaustiveness-check": "error",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }], "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error", eqeqeq: "error",

View File

@@ -22,5 +22,6 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": true,
"source.organizeImports": true "source.organizeImports": true
} },
"typescript.preferences.importModuleSpecifier": "non-relative"
} }

View File

@@ -1,16 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg <svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<path <path
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z" d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
transform="scale(0.5)" transform="scale(0.5)" fill="#000" stroke="#000">
fill="#000"
stroke="#CCC"
>
</path> </path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -37,8 +37,8 @@ module.exports = {
global: { global: {
branches: 25, branches: 25,
functions: 25, functions: 25,
lines: 29.5, lines: 29,
statements: 29.5, statements: 29,
}, },
}, },
@@ -129,6 +129,8 @@ module.exports = {
// The test environment that will be used for testing // The test environment that will be used for testing
// testEnvironment: "jest-environment-jsdom", // testEnvironment: "jest-environment-jsdom",
modulePaths: ["node_modules", "<rootDir>/src"],
// Options that will be passed to the testEnvironment // Options that will be passed to the testEnvironment
// testEnvironmentOptions: {}, // testEnvironmentOptions: {},

View File

@@ -2357,6 +2357,8 @@ a:link {
height: 100%; height: 100%;
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
min-height: 300px;
overflow-y: scroll;
} }
.tabs { .tabs {
@@ -2832,6 +2834,8 @@ a:link {
#explorerNotificationConsole { #explorerNotificationConsole {
z-index: 1000; z-index: 1000;
overflow-y: auto;
overflow-x: clip;
} }
.uniqueIndexesContainer { .uniqueIndexesContainer {

View File

@@ -1,384 +0,0 @@
export class CodeOfConductEndpoints {
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy";
public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct";
public static termsOfUse: string = "https://aka.ms/ms-terms-of-use";
}
export class EndpointsRegex {
public static readonly cassandra = [
"AccountEndpoint=(.*).cassandra.cosmosdb.azure.com",
"HostName=(.*).cassandra.cosmos.azure.com",
];
public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com";
public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com";
public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com";
public static readonly table = "TableEndpoint=https://(.*).table.cosmosdb.azure.com";
}
export class ApiEndpoints {
public static runtimeProxy: string = "/api/RuntimeProxy";
public static guestRuntimeProxy: string = "/api/guest/RuntimeProxy";
}
export class ServerIds {
public static localhost: string = "localhost";
public static blackforest: string = "blackforest";
public static fairfax: string = "fairfax";
public static mooncake: string = "mooncake";
public static productionPortal: string = "prod";
public static dev: string = "dev";
}
export class ArmApiVersions {
public static readonly documentDB: string = "2015-11-06";
public static readonly arcadia: string = "2019-06-01-preview";
public static readonly arcadiaLivy: string = "2019-11-01-preview";
public static readonly arm: string = "2015-11-01";
public static readonly armFeatures: string = "2014-08-01-preview";
public static readonly publicVersion = "2020-04-01";
}
export class ArmResourceTypes {
public static readonly notebookWorkspaces = "Microsoft.DocumentDB/databaseAccounts/notebookWorkspaces";
public static readonly synapseWorkspaces = "Microsoft.Synapse/workspaces";
}
export class BackendDefaults {
public static partitionKeyKind = "Hash";
public static singlePartitionStorageInGb: string = "10";
public static multiPartitionStorageInGb: string = "100";
public static maxChangeFeedRetentionDuration: number = 10;
public static partitionKeyVersion = 2;
}
export class ClientDefaults {
public static requestTimeoutMs: number = 60000;
public static portalCacheTimeoutMs: number = 10000;
public static errorNotificationTimeoutMs: number = 5000;
public static copyHelperTimeoutMs: number = 2000;
public static waitForDOMElementMs: number = 500;
public static cacheBustingTimeoutMs: number =
10 /** minutes **/ * 60 /** to seconds **/ * 1000 /** to milliseconds **/;
public static databaseThroughputIncreaseFactor: number = 100;
public static readonly arcadiaTokenRefreshInterval: number =
20 /** minutes **/ * 60 /** to seconds **/ * 1000 /** to milliseconds **/;
public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000;
}
export enum AccountKind {
DocumentDB = "DocumentDB",
MongoDB = "MongoDB",
Parse = "Parse",
GlobalDocumentDB = "GlobalDocumentDB",
Default = "DocumentDB",
}
export class CorrelationBackend {
public static Url: string = "https://aka.ms/cosmosdbanalytics";
}
export class CapabilityNames {
public static EnableTable: string = "EnableTable";
public static EnableGremlin: string = "EnableGremlin";
public static EnableCassandra: string = "EnableCassandra";
public static EnableAutoScale: string = "EnableAutoScale";
public static readonly EnableNotebooks: string = "EnableNotebooks";
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics";
public static readonly EnableMongo: string = "EnableMongo";
public static readonly EnableServerless: string = "EnableServerless";
}
// flight names returned from the portal are always lowercase
export class Flights {
public static readonly SettingsV2 = "settingsv2";
public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest";
public static readonly PartitionKeyTest = "partitionkeytest";
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
public static readonly Phoenix = "phoenix";
}
export class AfecFeatures {
public static readonly Spark = "spark-public-preview";
public static readonly Notebooks = "sparknotebooks-public-preview";
public static readonly StorageAnalytics = "storageanalytics-public-preview";
}
export class TagNames {
public static defaultExperience: string = "defaultExperience";
}
export class MongoDBAccounts {
public static protocol: string = "https";
public static defaultPort: string = "10255";
}
export enum MongoBackendEndpointType {
local,
remote,
}
// TODO: 435619 Add default endpoints per cloud and use regional only when available
export class CassandraBackend {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra";
public static readonly guestQueryApi: string = "api/guest/cassandra";
public static readonly keysApi: string = "api/cassandra/keys";
public static readonly guestKeysApi: string = "api/guest/cassandra/keys";
public static readonly schemaApi: string = "api/cassandra/schema";
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
}
export class Queries {
public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited";
public static itemsPerPage: number = 100;
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
public static QueryEditorMinHeightRatio: number = 0.1;
public static QueryEditorMaxHeightRatio: number = 0.4;
public static readonly DefaultMaxDegreeOfParallelism = 6;
}
export class SavedQueries {
public static readonly CollectionName: string = "___Query";
public static readonly DatabaseName: string = "___Cosmos";
public static readonly OfferThroughput: number = 400;
public static readonly PartitionKeyProperty: string = "id";
}
export class DocumentsGridMetrics {
public static DocumentsPerPage: number = 100;
public static IndividualRowHeight: number = 34;
public static BufferHeight: number = 28;
public static SplitterMinWidth: number = 200;
public static SplitterMaxWidth: number = 360;
public static DocumentEditorMinWidthRatio: number = 0.2;
public static DocumentEditorMaxWidthRatio: number = 0.4;
}
export class Areas {
public static ResourceTree: string = "Resource Tree";
public static ContextualPane: string = "Contextual Pane";
public static Tab: string = "Tab";
public static ShareDialog: string = "Share Access Dialog";
public static Notebook: string = "Notebook";
}
export class HttpHeaders {
public static activityId: string = "x-ms-activity-id";
public static apiType: string = "x-ms-cosmos-apitype";
public static authorization: string = "authorization";
public static collectionIndexTransformationProgress: string =
"x-ms-documentdb-collection-index-transformation-progress";
public static continuation: string = "x-ms-continuation";
public static correlationRequestId: string = "x-ms-correlation-request-id";
public static enableScriptLogging: string = "x-ms-documentdb-script-enable-logging";
public static guestAccessToken: string = "x-ms-encrypted-auth-token";
public static getReadOnlyKey: string = "x-ms-get-read-only-key";
public static connectionString: string = "x-ms-connection-string";
public static msDate: string = "x-ms-date";
public static location: string = "Location";
public static contentType: string = "Content-Type";
public static offerReplacePending: string = "x-ms-offer-replace-pending";
public static user: string = "x-ms-user";
public static populatePartitionStatistics: string = "x-ms-documentdb-populatepartitionstatistics";
public static queryMetrics: string = "x-ms-documentdb-query-metrics";
public static requestCharge: string = "x-ms-request-charge";
public static resourceQuota: string = "x-ms-resource-quota";
public static resourceUsage: string = "x-ms-resource-usage";
public static retryAfterMs: string = "x-ms-retry-after-ms";
public static scriptLogResults: string = "x-ms-documentdb-script-log-results";
public static populateCollectionThroughputInfo = "x-ms-documentdb-populatecollectionthroughputinfo";
public static supportSpatialLegacyCoordinates = "x-ms-documentdb-supportspatiallegacycoordinates";
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
public static autoPilotThroughput = "autoscaleSettings";
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
}
export class ApiType {
// Mapped to hexadecimal values in the backend
public static readonly MongoDB: number = 1;
public static readonly Gremlin: number = 2;
public static readonly Cassandra: number = 4;
public static readonly Table: number = 8;
public static readonly SQL: number = 16;
}
export class HttpStatusCodes {
public static readonly OK: number = 200;
public static readonly Created: number = 201;
public static readonly Accepted: number = 202;
public static readonly NoContent: number = 204;
public static readonly NotModified: number = 304;
public static readonly Unauthorized: number = 401;
public static readonly Forbidden: number = 403;
public static readonly NotFound: number = 404;
public static readonly TooManyRequests: number = 429;
public static readonly Conflict: number = 409;
public static readonly InternalServerError: number = 500;
public static readonly BadGateway: number = 502;
public static readonly ServiceUnavailable: number = 503;
public static readonly GatewayTimeout: number = 504;
public static readonly RetryableStatusCodes: number[] = [
HttpStatusCodes.TooManyRequests,
HttpStatusCodes.InternalServerError, // TODO: Handle all 500s on Portal backend and remove from retries list
HttpStatusCodes.BadGateway,
HttpStatusCodes.ServiceUnavailable,
HttpStatusCodes.GatewayTimeout,
];
}
export class Urls {
public static feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback";
public static autoscaleMigration = "https://aka.ms/cosmos-autoscale-migration";
public static freeTierInformation = "https://aka.ms/cosmos-free-tier";
public static cosmosPricing = "https://aka.ms/azure-cosmos-db-pricing";
}
export class HashRoutePrefixes {
public static databases: string = "/dbs/{db_id}";
public static collections: string = "/dbs/{db_id}/colls/{coll_id}";
public static sprocHash: string = "/sprocs/";
public static sprocs: string = HashRoutePrefixes.collections + HashRoutePrefixes.sprocHash + "{sproc_id}";
public static docs: string = HashRoutePrefixes.collections + "/docs/{doc_id}/";
public static conflicts: string = HashRoutePrefixes.collections + "/conflicts";
public static databasesWithId(databaseId: string): string {
return this.databases.replace("{db_id}", databaseId).replace("/", ""); // strip the first slash since hasher adds it
}
public static collectionsWithIds(databaseId: string, collectionId: string): string {
const transformedDatabasePrefix: string = this.collections.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it
}
public static sprocWithIds(
databaseId: string,
collectionId: string,
sprocId: string,
stripFirstSlash: boolean = true
): string {
const transformedDatabasePrefix: string = this.sprocs.replace("{db_id}", databaseId);
const transformedSprocRoute: string = transformedDatabasePrefix
.replace("{coll_id}", collectionId)
.replace("{sproc_id}", sprocId);
if (!!stripFirstSlash) {
return transformedSprocRoute.replace("/", ""); // strip the first slash since hasher adds it
}
return transformedSprocRoute;
}
public static conflictsWithIds(databaseId: string, collectionId: string) {
const transformedDatabasePrefix: string = this.conflicts.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it;
}
public static docsWithIds(databaseId: string, collectionId: string, docId: string) {
const transformedDatabasePrefix: string = this.docs.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("{doc_id}", docId).replace("/", ""); // strip the first slash since hasher adds it
}
}
export class ConfigurationOverridesValues {
public static IsBsonSchemaV2: string = "true";
}
export class KeyCodes {
public static Space: number = 32;
public static Enter: number = 13;
public static Escape: number = 27;
public static UpArrow: number = 38;
public static DownArrow: number = 40;
public static LeftArrow: number = 37;
public static RightArrow: number = 39;
public static Tab: number = 9;
}
// Normalized per: https://www.w3.org/TR/uievents-key/#named-key-attribute-values
export class NormalizedEventKey {
public static readonly Space = " ";
public static readonly Enter = "Enter";
public static readonly Escape = "Escape";
public static readonly UpArrow = "ArrowUp";
public static readonly DownArrow = "ArrowDown";
public static readonly LeftArrow = "ArrowLeft";
public static readonly RightArrow = "ArrowRight";
}
export class TryCosmosExperience {
public static extendUrl: string = "https://trycosmosdb.azure.com/api/resource/extendportal?userId={0}";
public static deleteUrl: string = "https://trycosmosdb.azure.com/api/resource/deleteportal?userId={0}";
public static collectionsPerAccount: number = 3;
public static maxRU: number = 5000;
public static defaultRU: number = 3000;
}
export class OfferVersions {
public static V1: string = "V1";
public static V2: string = "V2";
}
export enum ConflictOperationType {
Replace = "replace",
Create = "create",
Delete = "delete",
}
export enum ConnectionStatusType {
Connecting = "Connecting",
Connected = "Connected",
Failed = "Connection Failed",
}
export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
// A variable @MyVariable defined in Constants.less is accessible as StyleConstants.MyVariable
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
export class Notebook {
public static readonly defaultBasePath = "./notebooks";
public static readonly heartbeatDelayMs = 5000;
public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 120000;
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
public static readonly mongoShellTemporarilyDownMsg =
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
public static readonly cassandraShellTemporarilyDownMsg =
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
}
export class SparkLibrary {
public static readonly nameMinLength = 3;
public static readonly nameMaxLength = 63;
}
export class AnalyticalStorageTtl {
public static readonly Days90: number = 7776000;
public static readonly Infinite: number = -1;
public static readonly Disabled: number = 0;
}
export class TerminalQueryParams {
public static readonly Terminal = "terminal";
public static readonly Server = "server";
public static readonly Token = "token";
public static readonly SubscriptionId = "subscriptionId";
public static readonly TerminalEndpoint = "terminalEndpoint";
}

View File

@@ -0,0 +1,3 @@
export const Spark = "spark-public-preview";
export const Notebooks = "sparknotebooks-public-preview";
export const StorageAnalytics = "storageanalytics-public-preview";

View File

@@ -0,0 +1,3 @@
export const Days90 = 7776000;
export const Infinite = -1;
export const Disabled = 0;

View File

@@ -0,0 +1,2 @@
export const runtimeProxy = "/api/RuntimeProxy";
export const guestRuntimeProxy = "/api/guest/RuntimeProxy";

View File

@@ -0,0 +1,6 @@
// Mapped to hexadecimal values in the backend
export const MongoDB = 1;
export const Gremlin = 2;
export const Cassandra = 4;
export const Table = 8;
export const SQL = 16;

View File

@@ -0,0 +1,5 @@
export const ResourceTree = "Resource Tree";
export const ContextualPane = "Contextual Pane";
export const Tab = "Tab";
export const ShareDialog = "Share Access Dialog";
export const Notebook = "Notebook";

View File

@@ -0,0 +1,6 @@
export const documentDB = "2015-11-06";
export const arcadia = "2019-06-01-preview";
export const arcadiaLivy = "2019-11-01-preview";
export const arm = "2015-11-01";
export const armFeatures = "2014-08-01-preview";
export const publicVersion = "2020-04-01";

View File

@@ -0,0 +1,2 @@
export const notebookWorkspaces = "Microsoft.DocumentDB/databaseAccounts/notebookWorkspaces";
export const synapseWorkspaces = "Microsoft.Synapse/workspaces";

View File

@@ -0,0 +1,5 @@
export const partitionKeyKind = "Hash";
export const singlePartitionStorageInGb = "10";
export const multiPartitionStorageInGb = "100";
export const maxChangeFeedRetentionDuration = 10;
export const partitionKeyVersion = 2;

View File

@@ -0,0 +1,8 @@
export const EnableTable = "EnableTable";
export const EnableGremlin = "EnableGremlin";
export const EnableCassandra = "EnableCassandra";
export const EnableAutoScale = "EnableAutoScale";
export const EnableNotebooks = "EnableNotebooks";
export const EnableStorageAnalytics = "EnableStorageAnalytics";
export const EnableMongo = "EnableMongo";
export const EnableServerless = "EnableServerless";

View File

@@ -0,0 +1,9 @@
// TODO: 435619 Add default endpoints per cloud and use regional only when available
export const createOrDeleteApi = "api/cassandra/createordelete";
export const guestCreateOrDeleteApi = "api/guest/cassandra/createordelete";
export const queryApi = "api/cassandra";
export const guestQueryApi = "api/guest/cassandra";
export const keysApi = "api/cassandra/keys";
export const guestKeysApi = "api/guest/cassandra/keys";
export const schemaApi = "api/cassandra/schema";
export const guestSchemaApi = "api/guest/cassandra/schema";

View File

@@ -0,0 +1,9 @@
export const requestTimeoutMs = 60000;
export const portalCacheTimeoutMs = 10000;
export const errorNotificationTimeoutMs = 5000;
export const copyHelperTimeoutMs = 2000;
export const waitForDOMElementMs = 500;
export const cacheBustingTimeoutMs = 10 /** minutes **/ * 60 /** to seconds **/ * 1000; /** to milliseconds **/
export const databaseThroughputIncreaseFactor = 100;
export const arcadiaTokenRefreshInterval = 20 /** minutes **/ * 60 /** to seconds **/ * 1000; /** to milliseconds **/
export const arcadiaTokenRefreshIntervalPaddingMs = 2000;

View File

@@ -0,0 +1,3 @@
export const privacyStatement = "https://aka.ms/ms-privacy-policy";
export const codeOfConduct = "https://aka.ms/cosmos-code-of-conduct";
export const termsOfUse = "https://aka.ms/ms-terms-of-use";

View File

@@ -0,0 +1 @@
export const IsBsonSchemaV2 = "true";

View File

@@ -0,0 +1 @@
export const Url = "https://aka.ms/cosmosdbanalytics";

View File

@@ -0,0 +1,8 @@
export const DocumentsPerPage = 100;
export const IndividualRowHeight = 34;
export const BufferHeight = 28;
export const SplitterMinWidth = 200;
export const SplitterMaxWidth = 360;
export const DocumentEditorMinWidthRatio = 0.2;
export const DocumentEditorMaxWidthRatio = 0.4;

View File

@@ -0,0 +1,8 @@
export const cassandra = [
"AccountEndpoint=(.*).cassandra.cosmosdb.azure.com",
"HostName=(.*).cassandra.cosmos.azure.com",
];
export const mongo = "mongodb://.*:(.*)@(.*).documents.azure.com";
export const mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com";
export const sql = "AccountEndpoint=https://(.*).documents.azure.com";
export const table = "TableEndpoint=https://(.*).table.cosmosdb.azure.com";

View File

@@ -0,0 +1,8 @@
// flight names returned from the portal are always lowercase
export const SettingsV2 = "settingsv2";
export const MongoIndexEditor = "mongoindexeditor";
export const MongoIndexing = "mongoindexing";
export const AutoscaleTest = "autoscaletest";
export const PartitionKeyTest = "partitionkeytest";
export const PKPartitionKeyTest = "pkpartitionkeytest";
export const Phoenix = "phoenix";

View File

@@ -0,0 +1,40 @@
export const databases = "/dbs/{db_id}";
export const collections = "/dbs/{db_id}/colls/{coll_id}";
export const sprocHash = "/sprocs/";
export const sprocs = collections + sprocHash + "{sproc_id}";
export const docs = collections + "/docs/{doc_id}/";
export const conflicts = collections + "/conflicts";
export const databasesWithId = (databaseId: string) => {
return databases.replace("{db_id}", databaseId).replace("/", ""); // strip the first slash since hasher adds it
};
export const collectionsWithIds = (databaseId: string, collectionId: string) => {
const transformedDatabasePrefix = collections.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it
};
export const sprocWithIds = (databaseId: string, collectionId: string, sprocId: string, stripFirstSlash = true) => {
const transformedDatabasePrefix = sprocs.replace("{db_id}", databaseId);
const transformedSprocRoute = transformedDatabasePrefix
.replace("{coll_id}", collectionId)
.replace("{sproc_id}", sprocId);
if (stripFirstSlash) {
return transformedSprocRoute.replace("/", ""); // strip the first slash since hasher adds it
}
return transformedSprocRoute;
};
export const conflictsWithIds = (databaseId: string, collectionId: string) => {
const transformedDatabasePrefix = conflicts.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it;
};
export const docsWithIds = (databaseId: string, collectionId: string, docId: string): string => {
const transformedDatabasePrefix = docs.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("{doc_id}", docId).replace("/", ""); // strip the first slash since hasher adds it
};

View File

@@ -0,0 +1,30 @@
export const activityId = "x-ms-activity-id";
export const apiType = "x-ms-cosmos-apitype";
export const authorization = "authorization";
export const collectionIndexTransformationProgress = "x-ms-documentdb-collection-index-transformation-progress";
export const continuation = "x-ms-continuation";
export const correlationRequestId = "x-ms-correlation-request-id";
export const enableScriptLogging = "x-ms-documentdb-script-enable-logging";
export const guestAccessToken = "x-ms-encrypted-auth-token";
export const getReadOnlyKey = "x-ms-get-read-only-key";
export const connectionString = "x-ms-connection-string";
export const msDate = "x-ms-date";
export const location = "Location";
export const contentType = "Content-Type";
export const offerReplacePending = "x-ms-offer-replace-pending";
export const user = "x-ms-user";
export const populatePartitionStatistics = "x-ms-documentdb-populatepartitionstatistics";
export const queryMetrics = "x-ms-documentdb-query-metrics";
export const requestCharge = "x-ms-request-charge";
export const resourceQuota = "x-ms-resource-quota";
export const resourceUsage = "x-ms-resource-usage";
export const retryAfterMs = "x-ms-retry-after-ms";
export const scriptLogResults = "x-ms-documentdb-script-log-results";
export const populateCollectionThroughputInfo = "x-ms-documentdb-populatecollectionthroughputinfo";
export const supportSpatialLegacyCoordinates = "x-ms-documentdb-supportspatiallegacycoordinates";
export const usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
export const autoPilotThroughput = "autoscaleSettings";
export const autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
export const partitionKey = "x-ms-documentdb-partitionkey";
export const migrateOfferToManualThroughput = "x-ms-cosmos-migrate-offer-to-manual-throughput";
export const migrateOfferToAutopilot = "x-ms-cosmos-migrate-offer-to-autopilot";

View File

@@ -0,0 +1,23 @@
export const OK = 200;
export const Created = 201;
export const Accepted = 202;
export const NoContent = 204;
export const NotModified = 304;
export const Unauthorized = 401;
export const Forbidden = 403;
export const NotFound = 404;
export const TooManyRequests = 429;
export const Conflict = 409;
export const InternalServerError = 500;
export const BadGateway = 502;
export const ServiceUnavailable = 503;
export const GatewayTimeout = 504;
export const RetryableStatusCodes: number[] = [
TooManyRequests,
InternalServerError, // TODO: Handle all 500s on Portal backend and remove from retries list
BadGateway,
ServiceUnavailable,
GatewayTimeout,
];

View File

@@ -0,0 +1,8 @@
export const Space = 32;
export const Enter = 13;
export const Escape = 27;
export const UpArrow = 38;
export const DownArrow = 40;
export const LeftArrow = 37;
export const RightArrow = 39;
export const Tab = 9;

View File

@@ -0,0 +1,2 @@
export const protocol = "https";
export const defaultPort = "10255";

View File

@@ -0,0 +1,8 @@
// Normalized per: https://www.w3.org/TR/uievents-key/#named-key-attribute-values
export const Space = " ";
export const Enter = "Enter";
export const Escape = "Escape";
export const UpArrow = "ArrowUp";
export const DownArrow = "ArrowDown";
export const LeftArrow = "ArrowLeft";
export const RightArrow = "ArrowRight";

View File

@@ -0,0 +1,27 @@
export const defaultBasePath = "./notebooks";
export const heartbeatDelayMs = 60000;
export const kernelRestartInitialDelayMs = 1000;
export const kernelRestartMaxDelayMs = 20000;
export const autoSaveIntervalMs = 120000;
export const memoryGuageToGB = 1048576;
export const temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
export const mongoShellTemporarilyDownMsg =
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
export const cassandraShellTemporarilyDownMsg =
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
export const saveNotebookModalTitle = "Save Notebook in temporary workspace";
export const saveNotebookModalContent =
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
export const newNotebookModalTitle = "Create Notebook in temporary workspace";
export const newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
export const newNotebookModalContent1 =
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
export const newNotebookModalContent2 =
"To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends. ";
export const galleryNotebookDownloadContent1 =
"To download, run, and make changes to this sample notebook, a temporary workspace will be created. When the session expires, any notebooks in the workspace will be removed.";
export const galleryNotebookDownloadContent2 =
"To save your work permanently, save your notebooks to a GitHub repository or download the Notebooks to your local machine before the session ends. ";
export const cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
export const cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
export const learnMore = "Learn more.";

View File

@@ -0,0 +1,2 @@
export const V1 = "V1";
export const V2 = "V2";

View File

@@ -0,0 +1,8 @@
export const CustomPageOption = "custom";
export const UnlimitedPageOption = "unlimited";
export const itemsPerPage = 100;
export const unlimitedItemsPerPage = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
export const QueryEditorMinHeightRatio = 0.1;
export const QueryEditorMaxHeightRatio = 0.4;
export const DefaultMaxDegreeOfParallelism = 6;

View File

@@ -0,0 +1,4 @@
export const CollectionName = "___Query";
export const DatabaseName = "___Cosmos";
export const OfferThroughput = 400;
export const PartitionKeyProperty = "id";

View File

@@ -0,0 +1,6 @@
export const localhost = "localhost";
export const blackforest = "blackforest";
export const fairfax = "fairfax";
export const mooncake = "mooncake";
export const productionPortal = "prod";
export const dev = "dev";

View File

@@ -0,0 +1,2 @@
export const nameMinLength = 3;
export const nameMaxLength = 63;

View File

@@ -0,0 +1 @@
export const defaultExperience = "defaultExperience";

View File

@@ -0,0 +1,5 @@
export const Terminal = "terminal";
export const Server = "server";
export const Token = "token";
export const SubscriptionId = "subscriptionId";
export const TerminalEndpoint = "terminalEndpoint";

View File

@@ -0,0 +1,5 @@
export const extendUrl = "https://trycosmosdb.azure.com/api/resource/extendportal?userId={0}";
export const deleteUrl = "https://trycosmosdb.azure.com/api/resource/deleteportal?userId={0}";
export const collectionsPerAccount = 3;
export const maxRU = 5000;
export const defaultRU = 3000;

View File

@@ -0,0 +1,4 @@
export const feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback";
export const autoscaleMigration = "https://aka.ms/cosmos-autoscale-migration";
export const freeTierInformation = "https://aka.ms/cosmos-free-tier";
export const cosmosPricing = "https://aka.ms/azure-cosmos-db-pricing";

View File

@@ -0,0 +1,105 @@
import * as AfecFeatures from "./AfecFeatures";
import * as AnalyticalStorageTtl from "./AnalyticalStorageTtl";
import * as ApiEndpoints from "./ApiEndpoints";
import * as ApiType from "./ApiType";
import * as Areas from "./Areas";
import * as ArmApiVersions from "./ArmApiVersions";
import * as ArmResourceTypes from "./ArmResourceTypes";
import * as BackendDefaults from "./BackendDefaults";
import * as CapabilityNames from "./CapabilityNames";
import * as CassandraBackend from "./CassandraBackend";
import * as ClientDefaults from "./ClientDefaults";
import * as CodeOfConductEndpoints from "./CodeOfConductEndpoints";
import * as ConfigurationOverridesValues from "./ConfigurationOverridesValues";
import * as CorrelationBackend from "./CorrelationBackend";
import * as DocumentsGridMetrics from "./DocumentsGridMetrics";
import * as EndpointsRegex from "./EndpointsRegex";
import * as Flights from "./Flights";
import * as HashRoutePrefixes from "./HashRoutePrefixes";
import * as HttpHeaders from "./HttpHeaders";
import * as HttpStatusCodes from "./HttpStatusCodes";
import * as KeyCodes from "./KeyCodes";
import * as MongoDBAccounts from "./MongoDBAccounts";
import * as NormalizedEventKey from "./NormalizedEventKey";
import * as Notebook from "./Notebook";
import * as OfferVersions from "./OfferVersions";
import * as Queries from "./Queries";
import * as SavedQueries from "./SavedQueries";
import * as ServerIds from "./ServerIds";
import * as SparkLibrary from "./SparkLibrary";
import * as TagNames from "./TagNames";
import * as TerminalQueryParams from "./TerminalQueryParams";
import * as TryCosmosExperience from "./TryCosmosExperience";
import * as Urls from "./Urls";
const StyleConstants = require("less-vars-loader!../../../less/Common/Constants.less");
export {
StyleConstants,
SparkLibrary,
ConfigurationOverridesValues,
OfferVersions,
AnalyticalStorageTtl,
Notebook,
TryCosmosExperience,
NormalizedEventKey,
KeyCodes,
HashRoutePrefixes,
Urls,
HttpStatusCodes,
ApiType,
HttpHeaders,
Areas,
DocumentsGridMetrics,
SavedQueries,
Queries,
CassandraBackend,
MongoDBAccounts,
TagNames,
AfecFeatures,
Flights,
CorrelationBackend,
CapabilityNames,
ClientDefaults,
BackendDefaults,
ArmResourceTypes,
ArmApiVersions,
TerminalQueryParams,
CodeOfConductEndpoints,
ApiEndpoints,
EndpointsRegex,
ServerIds,
};
export enum ConnectionStatusType {
Connect = "Connect",
Connecting = "Connecting",
Connected = "Connected",
Failed = "Connection Failed",
ReConnect = "Reconnect",
}
export enum ConflictOperationType {
Replace = "replace",
Create = "create",
Delete = "delete",
}
export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
// A variable @MyVariable defined in Constants.less is accessible as StyleConstants.MyVariable
export enum AccountKind {
DocumentDB = "DocumentDB",
MongoDB = "MongoDB",
Parse = "Parse",
GlobalDocumentDB = "GlobalDocumentDB",
Default = "DocumentDB",
}
export enum MongoBackendEndpointType {
local,
remote,
}

View File

@@ -80,8 +80,7 @@ export function queryDocuments(
}; };
const endpoint = getFeatureEndpointOrDefault("resourcelist") || ""; const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
const headers: HeadersInit = {
const headers = {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[CosmosSDKConstants.HttpHeaders.IsQuery]: "true", [CosmosSDKConstants.HttpHeaders.IsQuery]: "true",

View File

@@ -13,6 +13,7 @@ import {
Link, Link,
PrimaryButton, PrimaryButton,
ProgressIndicator, ProgressIndicator,
Text,
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import React, { FC } from "react"; import React, { FC } from "react";
@@ -30,6 +31,7 @@ export interface DialogState {
onOk: () => void, onOk: () => void,
cancelLabel: string, cancelLabel: string,
onCancel: () => void, onCancel: () => void,
contentHtml?: JSX.Element,
choiceGroupProps?: IChoiceGroupProps, choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean primaryButtonDisabled?: boolean
@@ -58,6 +60,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
onOk: () => void, onOk: () => void,
cancelLabel: string, cancelLabel: string,
onCancel: () => void, onCancel: () => void,
contentHtml?: JSX.Element,
choiceGroupProps?: IChoiceGroupProps, choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean primaryButtonDisabled?: boolean
@@ -76,6 +79,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
get().closeDialog(); get().closeDialog();
onCancel && onCancel(); onCancel && onCancel();
}, },
contentHtml,
choiceGroupProps, choiceGroupProps,
textFieldProps, textFieldProps,
primaryButtonDisabled, primaryButtonDisabled,
@@ -124,6 +128,7 @@ export interface DialogProps {
type?: DialogType; type?: DialogType;
showCloseButton?: boolean; showCloseButton?: boolean;
onDismiss?: () => void; onDismiss?: () => void;
contentHtml?: JSX.Element;
} }
const DIALOG_MIN_WIDTH = "400px"; const DIALOG_MIN_WIDTH = "400px";
@@ -150,6 +155,7 @@ export const Dialog: FC = () => {
type, type,
showCloseButton, showCloseButton,
onDismiss, onDismiss,
contentHtml,
} = props || {}; } = props || {};
const dialogProps: IDialogProps = { const dialogProps: IDialogProps = {
@@ -191,6 +197,7 @@ export const Dialog: FC = () => {
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" /> {linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
)} )}
{contentHtml && <Text>{contentHtml}</Text>}
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />} {progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
<DialogFooter> <DialogFooter>
<PrimaryButton {...primaryButtonProps} /> <PrimaryButton {...primaryButtonProps} />

View File

@@ -8,6 +8,9 @@
.input-type-head-text-field { .input-type-head-text-field {
width: 100%; width: 100%;
} }
.input-query-form {
width: 100%;
}
textarea { textarea {
width: 100%; width: 100%;
line-height: 1; line-height: 1;

View File

@@ -160,18 +160,21 @@ export class InputTypeaheadComponent extends React.Component<
return ( return (
<div className="input-typeahead-container"> <div className="input-typeahead-container">
<Stack horizontal> <Stack horizontal>
<TextField <form aria-labelledby="input" className="input-query-form">
multiline={useTextarea} <TextField
rows={1} multiline={useTextarea}
defaultValue={defaultValue} rows={1}
ariaLabel="Input query" id="input"
placeholder={placeholder} defaultValue={defaultValue}
className="input-type-head-text-field" ariaLabel="Input query"
value={defaultValue} placeholder={placeholder}
onKeyDown={this.onSubmit} className="input-type-head-text-field"
onFocus={() => this.setState({ isSuggestionVisible: true })} value={defaultValue}
onChange={(_event, newValue?: string) => this.handleChange(newValue)} onKeyDown={this.onSubmit}
/> onFocus={() => this.setState({ isSuggestionVisible: true })}
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
/>
</form>
{this.props.showCancelButton && ( {this.props.showCancelButton && (
<IconButton <IconButton
styles={iconButtonStyles} styles={iconButtonStyles}

View File

@@ -7,16 +7,22 @@ exports[`inputTypeahead renders <input /> 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
> >
<StyledTextFieldBase <form
ariaLabel="Input query" aria-labelledby="input"
className="input-type-head-text-field" className="input-query-form"
multiline={false} >
onChange={[Function]} <StyledTextFieldBase
onFocus={[Function]} ariaLabel="Input query"
onKeyDown={[Function]} className="input-type-head-text-field"
placeholder="placeholder" id="input"
rows={1} multiline={false}
/> onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="placeholder"
rows={1}
/>
</form>
</Stack> </Stack>
</div> </div>
`; `;
@@ -28,16 +34,22 @@ exports[`inputTypeahead renders <textarea /> 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
> >
<StyledTextFieldBase <form
ariaLabel="Input query" aria-labelledby="input"
className="input-type-head-text-field" className="input-query-form"
multiline={true} >
onChange={[Function]} <StyledTextFieldBase
onFocus={[Function]} ariaLabel="Input query"
onKeyDown={[Function]} className="input-type-head-text-field"
placeholder="placeholder" id="input"
rows={1} multiline={true}
/> onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="placeholder"
rows={1}
/>
</form>
</Stack> </Stack>
</div> </div>
`; `;

View File

@@ -17,6 +17,8 @@ import Explorer from "../../Explorer";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { NotebookUtil } from "../../Notebook/NotebookUtil";
import { useNotebook } from "../../Notebook/useNotebook";
import { Dialog, TextFieldProps, useDialog } from "../Dialog"; import { Dialog, TextFieldProps, useDialog } from "../Dialog";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
@@ -146,7 +148,9 @@ export class NotebookViewerComponent
<NotebookMetadataComponent <NotebookMetadataComponent
data={this.state.galleryItem} data={this.state.galleryItem}
isFavorite={this.state.isFavorite} isFavorite={this.state.isFavorite}
downloadButtonText={this.props.container && "Download to my notebooks"} downloadButtonText={
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
}
onTagClick={this.props.onTagClick} onTagClick={this.props.onTagClick}
onFavoriteClick={this.favoriteItem} onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem} onUnfavoriteClick={this.unfavoriteItem}

View File

@@ -65,7 +65,7 @@ export interface PriceBreakdown {
currencySign: string; currencySign: string;
} }
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } }; export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
label: { label: {
@@ -272,7 +272,7 @@ export const manualToAutoscaleDisclaimerElement: JSX.Element = (
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement"> <Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "} and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
<a href={Urls.autoscaleMigration}>Learn more</a> <Link href={Urls.autoscaleMigration}>Learn more</Link>
</Text> </Text>
); );

View File

@@ -8,6 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -20,6 +20,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -39,6 +40,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -73,6 +75,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -80,11 +83,11 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
> >
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
<a <StyledLinkBase
href="https://aka.ms/cosmos-autoscale-migration" href="https://aka.ms/cosmos-autoscale-migration"
> >
Learn more Learn more
</a> </StyledLinkBase>
</Text> </Text>
</StyledMessageBar> </StyledMessageBar>
<StyledChoiceGroup <StyledChoiceGroup
@@ -186,6 +189,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -460,6 +464,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -16,6 +16,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -136,6 +136,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -412,6 +413,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -952,6 +954,7 @@ exports[`SubSettingsComponent renders 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -1228,6 +1231,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -159,6 +159,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -166,16 +167,17 @@ exports[`SettingsUtils functions render 1`] = `
> >
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
<a <StyledLinkBase
href="https://aka.ms/cosmos-autoscale-migration" href="https://aka.ms/cosmos-autoscale-migration"
> >
Learn more Learn more
</a> </StyledLinkBase>
</Text> </Text>
<Text <Text
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -195,6 +197,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -207,6 +210,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -219,6 +223,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -230,6 +235,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -249,6 +255,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -264,6 +271,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -278,6 +286,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -291,6 +300,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -302,6 +312,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -321,6 +332,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -363,6 +375,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -378,6 +391,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -394,6 +408,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -1,7 +1,6 @@
@import "../../../../less/Common/Constants"; @import "../../../../less/Common/Constants";
.tabComponentContainer { .tabComponentContainer {
overflow: hidden;
height: 100%; height: 100%;
.flex-display(); .flex-display();
.flex-direction(); .flex-direction();

View File

@@ -243,6 +243,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
<div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}> <div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}>
<IconButton <IconButton
name="More" name="More"
title="More"
className="treeMenuEllipsis" className="treeMenuEllipsis"
ariaLabel={menuItemLabel} ariaLabel={menuItemLabel}
menuIconProps={{ menuIconProps={{

View File

@@ -211,6 +211,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
}, },
} }
} }
title="More"
/> />
</div> </div>
</div> </div>
@@ -423,6 +424,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
}, },
} }
} }
title="More"
/> />
</div> </div>
</div> </div>

View File

@@ -1,9 +1,11 @@
import { Link } from "@fluentui/react/lib/Link";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
import _ from "underscore"; import _ from "underscore";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
import { readCollection } from "../Common/dataAccess/readCollection"; import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases"; import { readDatabases } from "../Common/dataAccess/readDatabases";
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
@@ -11,6 +13,7 @@ import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHand
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { ContainerConnectionInfo } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
@@ -163,23 +166,10 @@ export default class Explorer {
useNotebook.subscribe( useNotebook.subscribe(
async () => { async () => {
if (!this.notebookManager) { this.initiateAndRefreshNotebookList();
const NotebookManager = await ( useNotebook.getState().setIsRefreshed(false);
await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager")
).default;
this.notebookManager = new NotebookManager();
this.notebookManager.initialize({
container: this,
resourceTree: this.resourceTree,
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
refreshNotebookList: () => this.refreshNotebookList(),
});
}
this.refreshCommandBarButtons();
this.refreshNotebookList();
}, },
(state) => state.isNotebookEnabled (state) => state.isNotebookEnabled || state.isRefreshed
); );
this.resourceTree = new ResourceTreeAdapter(this); this.resourceTree = new ResourceTreeAdapter(this);
@@ -212,6 +202,23 @@ export default class Explorer {
this.refreshExplorer(); this.refreshExplorer();
} }
public async initiateAndRefreshNotebookList(): Promise<void> {
if (!this.notebookManager) {
const NotebookManager = (await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager"))
.default;
this.notebookManager = new NotebookManager();
this.notebookManager.initialize({
container: this,
resourceTree: this.resourceTree,
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
refreshNotebookList: () => this.refreshNotebookList(),
});
}
this.refreshCommandBarButtons();
this.refreshNotebookList();
}
public openEnableSynapseLinkDialog(): void { public openEnableSynapseLinkDialog(): void {
const addSynapseLinkDialogProps: DialogProps = { const addSynapseLinkDialogProps: DialogProps = {
linkProps: { linkProps: {
@@ -345,23 +352,7 @@ export default class Explorer {
return; return;
} }
this._isInitializingNotebooks = true; this._isInitializingNotebooks = true;
if (userContext.features.phoenix) { if (userContext.features.phoenix === false) {
const provisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
resourceId: userContext.databaseAccount.id,
dbAccountName: userContext.databaseAccount.name,
aadToken: userContext.authorizationToken,
resourceGroup: userContext.resourceGroup,
subscriptionId: userContext.subscriptionId,
};
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
});
}
} else {
await this.ensureNotebookWorkspaceRunning(); await this.ensureNotebookWorkspaceRunning();
const connectionInfo = await listConnectionInfo( const connectionInfo = await listConnectionInfo(
userContext.subscriptionId, userContext.subscriptionId,
@@ -376,13 +367,59 @@ export default class Explorer {
}); });
} }
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
this.refreshNotebookList(); this.refreshNotebookList();
this._isInitializingNotebooks = false; this._isInitializingNotebooks = false;
} }
public async allocateContainer(): Promise<void> {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
const isAllocating = useNotebook.getState().isAllocating;
if (isAllocating === false && notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined) {
const provisionData = {
aadToken: userContext.authorizationToken,
subscriptionId: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
dbAccountName: userContext.databaseAccount.name,
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
};
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Connecting,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
try {
useNotebook.getState().setIsAllocating(true);
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
if (
connectionInfo.status === HttpStatusCodes.OK &&
connectionInfo.data &&
connectionInfo.data.notebookServerUrl
) {
connectionStatus.status = ConnectionStatusType.Connected;
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
});
this.notebookManager?.notebookClient
.getMemoryUsage()
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
useNotebook.getState().setIsAllocating(false);
} else {
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetConatinerConnection(connectionStatus);
}
} catch (error) {
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetConatinerConnection(connectionStatus);
throw error;
}
this.refreshNotebookList();
this._isInitializingNotebooks = false;
}
}
public resetNotebookWorkspace(): void { public resetNotebookWorkspace(): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
handleError( handleError(
@@ -654,6 +691,9 @@ export default class Explorer {
if (!notebookContentItem || !notebookContentItem.path) { if (!notebookContentItem || !notebookContentItem.path) {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
} }
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
this.allocateContainer();
}
const notebookTabs = useTabs const notebookTabs = useTabs
.getState() .getState()
@@ -875,9 +915,51 @@ export default class Explorer {
handleError(error, "Explorer/onNewNotebookClicked"); handleError(error, "Explorer/onNewNotebookClicked");
throw new Error(error); throw new Error(error);
} }
const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled();
if (isPhoenixEnabled) {
if (isGithubTree) {
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
};
} else {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookModalTitle,
undefined,
"Create",
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
},
"Cancel",
undefined,
this.getNewNoteWarningText()
);
}
} else {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
}
}
parent = parent || this.resourceTree.myNotebooksContentRoot; private getNewNoteWarningText(): JSX.Element {
return (
<>
<p>{Notebook.newNotebookModalContent1}</p>
<br />
<p>
{Notebook.newNotebookModalContent2}
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
{Notebook.learnMore}
</Link>
</p>
</>
);
}
private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void {
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`); const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, { const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
@@ -924,7 +1006,26 @@ export default class Explorer {
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item); await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
} }
public openNotebookTerminal(kind: ViewModels.TerminalKind): void { public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
if (NotebookUtil.isPhoenixEnabled()) {
await this.allocateContainer();
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
this.connectToNotebookTerminal(kind);
} else {
useDialog
.getState()
.showOkModalDialog(
"Failed to Connect",
"Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
);
}
} else {
this.connectToNotebookTerminal(kind);
}
}
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
let title: string; let title: string;
switch (kind) { switch (kind) {
@@ -975,7 +1076,7 @@ export default class Explorer {
notebookUrl?: string, notebookUrl?: string,
galleryItem?: IGalleryItem, galleryItem?: IGalleryItem,
isFavorite?: boolean isFavorite?: boolean
) { ): Promise<void> {
const title = "Gallery"; const title = "Gallery";
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default; const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
const galleryTab = useTabs const galleryTab = useTabs
@@ -1079,7 +1180,27 @@ export default class Explorer {
} }
public openUploadFilePanel(parent?: NotebookContentItem): void { public openUploadFilePanel(parent?: NotebookContentItem): void {
parent = parent || this.resourceTree.myNotebooksContentRoot; if (NotebookUtil.isPhoenixEnabled()) {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookUploadModalTitle,
undefined,
"Upload",
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.uploadFilePanel(parent);
},
"Cancel",
undefined,
this.getNewNoteWarningText()
);
} else {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.uploadFilePanel(parent);
}
}
private uploadFilePanel(parent?: NotebookContentItem): void {
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
@@ -1088,6 +1209,24 @@ export default class Explorer {
); );
} }
public getDownloadModalConent(fileName: string): JSX.Element {
if (NotebookUtil.isPhoenixEnabled()) {
return (
<>
<p>{Notebook.galleryNotebookDownloadContent1}</p>
<br />
<p>
{Notebook.galleryNotebookDownloadContent2}
<Link href={Notebook.cosmosNotebookGitDocumentationUrl} target="_blank">
{Notebook.learnMore}
</Link>
</p>
</>
);
}
return <p> Download {fileName} from gallery as a copy to your notebooks to run and/or edit the notebook. </p>;
}
public async refreshExplorer(): Promise<void> { public async refreshExplorer(): Promise<void> {
userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Q from "q"; import * as Q from "q";
import * as React from "react"; import * as React from "react";
@@ -294,8 +296,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setGremlinParams(); this.setGremlinParams();
} }
const selectedNode = this.state.highlightedNode;
props.onGraphAccessorCreated({ props.onGraphAccessorCreated({
applyFilter: this.submitQuery.bind(this), applyFilter: this.submitQuery.bind(this),
addVertex: this.addVertex.bind(this), addVertex: this.addVertex.bind(this),
@@ -303,7 +303,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}); });
} // constructor } // constructor
public shareIGraphConfig(igraphConfig: IGraphConfig) { public shareIGraphConfig(igraphConfig: IGraphConfig): void {
this.setState({ this.setState({
igraphConfig: { ...igraphConfig }, igraphConfig: { ...igraphConfig },
}); });
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const partitionKeyProperty = this.props.collectionPartitionKeyProperty; const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
// aggregate all the properties, remove dropped ones // aggregate all the properties, remove dropped ones
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties); const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
// Compose the query // Compose the query
let pkId = editedProperties.pkId; const pkId = editedProperties.pkId;
let updateQueryFragment = ""; let updateQueryFragment = "";
finalProperties.forEach((p) => { finalProperties.forEach((p) => {
@@ -422,7 +422,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Called from ko binding * Called from ko binding
* @param id * @param id
*/ */
public selectNode(id: string) { public selectNode(id: string): void {
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet."); console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
return; return;
@@ -431,7 +431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.d3ForceGraph.selectNode(id); this.d3ForceGraph.selectNode(id);
} }
public deleteHighlightedNode() { public deleteHighlightedNode(): void {
if (!this.state.highlightedNode) { if (!this.state.highlightedNode) {
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove."); GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
return; return;
@@ -467,23 +467,23 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Is of type: {e: GremlinEdge, v: GremlinVertex}[] * Is of type: {e: GremlinEdge, v: GremlinVertex}[]
* @param data * @param data
*/ */
public static isEdgeVertexPairArray(data: any) { public static isEdgeVertexPairArray(data: any): boolean {
if (!(data instanceof Array)) { if (!(data instanceof Array)) {
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data); GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
return false; return false;
} }
let pairs: any[] = data; const pairs: any[] = data;
for (let i = 0; i < pairs.length; i++) { for (let i = 0; i < pairs.length; i++) {
const item = pairs[i]; const item = pairs[i];
if ( if (
!item.hasOwnProperty("e") || !Object.prototype.hasOwnProperty.call(item, "e") ||
!item.hasOwnProperty("v") || !Object.prototype.hasOwnProperty.call(item, "v") ||
!item["e"].hasOwnProperty("id") || !Object.prototype.hasOwnProperty.call(item["e"], "id") ||
!item["e"].hasOwnProperty("type") || !Object.prototype.hasOwnProperty.call(item["e"], "type") ||
item["e"].type !== "edge" || item["e"].type !== "edge" ||
!item["v"].hasOwnProperty("id") || !Object.prototype.hasOwnProperty.call(item["v"], "id") ||
!item["v"].hasOwnProperty("type") || !Object.prototype.hasOwnProperty.call(item["e"], "type") ||
item["v"].type !== "vertex" item["v"].type !== "vertex"
) { ) {
return false; return false;
@@ -514,7 +514,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// Try hitting cache first // Try hitting cache first
const cache = outE ? this.outECache : this.inECache; const cache = outE ? this.outECache : this.inECache;
const pairs = cache.retrieve(vertex.id, startIndex, pageSize); const pairs = cache.retrieve(vertex.id, startIndex, pageSize);
if (pairs != null && pairs.length === pageSize) { if (pairs !== null && pairs.length === pageSize) {
const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`; const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`;
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg); GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
return Q.resolve(pairs); return Q.resolve(pairs);
@@ -588,7 +588,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
vertex._outEAllLoaded && vertex._outEAllLoaded &&
vertex._inEAllLoaded vertex._inEAllLoaded
) { ) {
console.info("No more edges to load for vertex " + vertex.id);
updateGraphData(); updateGraphData();
return Q.resolve(graphData); return Q.resolve(graphData);
} }
@@ -668,7 +667,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
} }
); );
return promise.then((nbPairsFetched: number) => { return promise.then(() => {
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) { if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
vertex._pagination = { vertex._pagination = {
total: total:
@@ -754,7 +753,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Create a new edge in docdb and update graph * Create a new edge in docdb and update graph
* @param e * @param e
*/ */
public createNewEdge(e: GraphNewEdgeData): Q.Promise<any> { public createNewEdge(e: GraphNewEdgeData): Q.Promise<unknown> {
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes( const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
e.label e.label
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`; )}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
@@ -772,8 +771,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let edge = edges[0]; const edge = edges[0];
let graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.addEdge(edge); graphData.addEdge(edge);
// Allow loadNeighbors to load list new edge // Allow loadNeighbors to load list new edge
@@ -800,10 +799,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Manually update in-memory graph. * Manually update in-memory graph.
* @param edgeId * @param edgeId
*/ */
public removeEdge(edgeId: string): Q.Promise<any> { public removeEdge(edgeId: string): Q.Promise<unknown> {
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then( return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
() => { () => {
let graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.removeEdge(edgeId, false); graphData.removeEdge(edgeId, false);
this.updateGraphData(graphData, this.state.igraphConfig); this.updateGraphData(graphData, this.state.igraphConfig);
}, },
@@ -826,10 +825,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return false; return false;
} }
let vertices: any[] = data; const vertices: any[] = data;
if (vertices.length > 0) { if (vertices.length > 0) {
let v0 = vertices[0]; const v0 = vertices[0];
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") { if (
!Object.prototype.hasOwnProperty.call(v0, "id") ||
!Object.prototype.hasOwnProperty.call(v0, "type") ||
v0.type !== "vertex"
) {
return false; return false;
} }
} }
@@ -837,7 +840,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
} }
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void { public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
const data = result.data as any; const data = result.data as GraphData.GremlinVertex[];
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult); this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
if (data === null) { if (data === null) {
@@ -927,13 +930,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
throw { title: err }; throw { title: err };
} }
if (vertices == null || vertices.length < 1) { if (vertices === null || vertices.length < 1) {
const err = "Failed to create vertex (no vertex in response)"; const err = "Failed to create vertex (no vertex in response)";
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices); GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
throw { title: err }; throw { title: err };
} }
let vertex = vertices[0]; const vertex = vertices[0];
const graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.addVertex(vertex); graphData.addVertex(vertex);
this.updateGraphData(graphData, this.state.igraphConfig); this.updateGraphData(graphData, this.state.igraphConfig);
@@ -1022,7 +1025,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.gremlinClient.destroy(); this.gremlinClient.destroy();
} }
public componentDidMount(): void { public componentDidMount(): void {
if (this.props.onLoadStartKey != null && this.props.onLoadStartKey != undefined) { if (this.props.onLoadStartKey !== null && this.props.onLoadStartKey !== undefined) {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.Tab, Action.Tab,
{ {
@@ -1082,7 +1085,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) { public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
let errorDataStr: string = ""; let errorDataStr = "";
if (errorData && errorData.length > 0) { if (errorData && errorData.length > 0) {
console.error(msg, errorData); console.error(msg, errorData);
errorDataStr = ": " + JSON.stringify(errorData); errorDataStr = ": " + JSON.stringify(errorData);
@@ -1161,12 +1164,15 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
)}"` )}"`
).then( ).then(
(documents: DataModels.DocumentId[]) => { (documents: DataModels.DocumentId[]) => {
$.each(documents, (index: number, doc: any) => { $.each(
newIconsMap[doc["_graph_icon_property_value"]] = { documents,
data: doc["icon"], (index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
format: doc["format"], newIconsMap[doc["_graph_icon_property_value"]] = {
}; data: doc["icon"],
}); format: doc["format"],
};
}
);
// Update graph configuration // Update graph configuration
this.setState({ this.setState({
@@ -1223,8 +1229,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const key = this.state.igraphConfig.nodeCaption; const key = this.state.igraphConfig.nodeCaption;
return $.map( return $.map(
this.state.rootMap, this.state.rootMap,
(value: any, index: number): LeftPane.CaptionId => { (value: any): LeftPane.CaptionId => {
let result = GraphData.GraphData.getNodePropValue(value, key); const result = GraphData.GraphData.getNodePropValue(value, key);
return { return {
caption: result !== undefined ? result : value.id, caption: result !== undefined ? result : value.id,
id: value.id, id: value.id,
@@ -1237,7 +1243,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Selecting a root node means * Selecting a root node means
* @param node * @param node
*/ */
private selectRootNode(id: string): Q.Promise<any> { private selectRootNode(id: string): Q.Promise<unknown> {
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet."); console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
} else { } else {
@@ -1282,7 +1288,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.collectNodeProperties(this.originalGraphData.vertices); this.collectNodeProperties(this.originalGraphData.vertices);
this.updatePropertiesPane(id); this.updatePropertiesPane(id);
}, },
(reason: any) => { (reason: string) => {
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`); GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
} }
); );
@@ -1349,10 +1355,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private getPkIdFromVertex(v: GraphData.GremlinVertex): string { private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
if ( if (
this.props.collectionPartitionKeyProperty && this.props.collectionPartitionKeyProperty &&
v.hasOwnProperty("properties") && Object.prototype.hasOwnProperty.call(v, "properties") &&
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) && Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty) &&
v.properties[this.props.collectionPartitionKeyProperty].length > 0 && v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
v.properties[this.props.collectionPartitionKeyProperty][0].hasOwnProperty("value") Object.prototype.hasOwnProperty.call(v.properties[this.props.collectionPartitionKeyProperty][0], "value")
) { ) {
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value; const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
return GraphExplorer.generatePkIdPair(pk, v.id); return GraphExplorer.generatePkIdPair(pk, v.id);
@@ -1370,8 +1376,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string { private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
if ( if (
this.props.collectionPartitionKeyProperty && this.props.collectionPartitionKeyProperty &&
v.hasOwnProperty("properties") && Object.prototype.hasOwnProperty.call(v, "properties") &&
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty)
) { ) {
const pk = v.properties[this.props.collectionPartitionKeyProperty]; const pk = v.properties[this.props.collectionPartitionKeyProperty];
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id); return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
@@ -1388,14 +1394,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @return id * @return id
*/ */
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string { public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
let { id } = d; const { id } = d;
if (typeof id !== "string") { if (typeof id !== "string") {
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`; const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
logConsoleError(error); logConsoleError(error);
throw new Error(error); throw new Error(error);
} }
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) { if (collectionPartitionKeyProperty && Object.prototype.hasOwnProperty.call(d, collectionPartitionKeyProperty)) {
let pk = (d as any)[collectionPartitionKeyProperty]; let pk = (d as any)[collectionPartitionKeyProperty];
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") { if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
if (Array.isArray(pk) && pk.length > 0) { if (Array.isArray(pk) && pk.length > 0) {
@@ -1425,7 +1431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`; }"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
return this.executeNonPagedDocDbQuery(q).then( return this.executeNonPagedDocDbQuery(q).then(
(documents: DataModels.DocumentId[]) => { (documents: DataModels.DocumentId[]) => {
let possibleVertices = [] as PossibleVertex[]; const possibleVertices = [] as PossibleVertex[];
$.each(documents, (index: number, item: any) => { $.each(documents, (index: number, item: any) => {
if (highlightedNodeId && item.id === highlightedNodeId) { if (highlightedNodeId && item.id === highlightedNodeId) {
// Exclude highlighed node in the list // Exclude highlighed node in the list
@@ -1439,7 +1445,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
caption: item.p, caption: item.p,
}); });
} else { } else {
if (item.hasOwnProperty("p")) { if (Object.prototype.hasOwnProperty.call(item, "p")) {
possibleVertices.push({ possibleVertices.push({
value: item.id, value: item.id,
caption: item.p[0]["_value"], caption: item.p[0]["_value"],
@@ -1462,17 +1468,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @param addedEdges * @param addedEdges
* @return promise when done * @return promise when done
*/ */
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> { private editGraphEdges(editedEdges: EditedEdges): Q.Promise<unknown> {
let promises = []; const promises = [];
// Drop edges // Drop edges
for (let i = 0; i < editedEdges.droppedIds.length; i++) { for (let i = 0; i < editedEdges.droppedIds.length; i++) {
let id = editedEdges.droppedIds[i]; const id = editedEdges.droppedIds[i];
promises.push(this.removeEdge(id)); promises.push(this.removeEdge(id));
} }
// Add edges // Add edges
for (let i = 0; i < editedEdges.addedEdges.length; i++) { for (let i = 0; i < editedEdges.addedEdges.length; i++) {
let e = editedEdges.addedEdges[i]; const e = editedEdges.addedEdges[i];
promises.push( promises.push(
this.createNewEdge(e).then(() => { this.createNewEdge(e).then(() => {
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph // Reload neighbors in case we linked to a vertex that isn't loaded in the graph
@@ -1525,7 +1531,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
/** /**
* For unit testing purposes * For unit testing purposes
*/ */
public onGraphUpdated(timestamp: number): void {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public onGraphUpdated(_timestamp: number): void {}
/** /**
* Get node properties for styling purposes. Result is the union of all properties of all nodes. * Get node properties for styling purposes. Result is the union of all properties of all nodes.
@@ -1533,17 +1541,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) { private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
const props = {} as any; // Hashset const props = {} as any; // Hashset
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => { $.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
for (var p in item) { for (const p in item) {
// DocDB: Exclude type because it's always 'vertex' // DocDB: Exclude type because it's always 'vertex'
if (p !== "type" && typeof (item as any)[p] === "string") { if (p !== "type" && typeof (item as any)[p] === "string") {
props[p] = true; props[p] = true;
} }
} }
// Inspect properties // Inspect properties
if (item.hasOwnProperty("properties")) { if (Object.prototype.hasOwnProperty.call(item, "properties")) {
// TODO This is DocDB-graph specific // TODO This is DocDB-graph specific
// Assume each property value is [{value:... }] // Assume each property value is [{value:... }]
for (var f in item.properties) { for (const f in item.properties) {
props[f] = true; props[f] = true;
} }
} }
@@ -1570,21 +1578,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let data = this.originalGraphData.getVertexById(id); const data = this.originalGraphData.getVertexById(id);
// A bit of translation to make it easier to display // A bit of translation to make it easier to display
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {}; const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
for (let p in data.properties) { for (const p in data.properties) {
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value); props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
} }
// update neighbors // update neighbors
let sources: NeighborVertexBasicInfo[] = []; const sources: NeighborVertexBasicInfo[] = [];
let targets: NeighborVertexBasicInfo[] = []; const targets: NeighborVertexBasicInfo[] = [];
this.props.onResetDefaultGraphConfigValues(); this.props.onResetDefaultGraphConfigValues();
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice; const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets); this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
let sData: GraphHighlightedNodeData = { const sData: GraphHighlightedNodeData = {
id: data.id, id: data.id,
label: data.label, label: data.label,
properties: props, properties: props,
@@ -1611,16 +1619,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
targets: NeighborVertexBasicInfo[] targets: NeighborVertexBasicInfo[]
): void { ): void {
// update neighbors // update neighbors
let gd = this.originalGraphData; const gd = this.originalGraphData;
let v = gd.getVertexById(id); const v = gd.getVertexById(id);
// Clear the array while keeping the references // Clear the array while keeping the references
sources.length = 0; sources.length = 0;
targets.length = 0; targets.length = 0;
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
for (let p in v.inE) { for (const p in v.inE) {
possibleEdgeLabels[p] = true; possibleEdgeLabels[p] = true;
const edges = v.inE[p]; const edges = v.inE[p];
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => { $.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
@@ -1629,7 +1637,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet // If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
return; return;
} }
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string; const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
sources.push({ sources.push({
name: caption, name: caption,
id: neighborId, id: neighborId,
@@ -1639,7 +1647,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}); });
} }
for (let p in v.outE) { for (const p in v.outE) {
possibleEdgeLabels[p] = true; possibleEdgeLabels[p] = true;
const edges = v.outE[p]; const edges = v.outE[p];
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => { $.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
@@ -1648,7 +1656,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet // If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
return; return;
} }
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string; const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
targets.push({ targets.push({
name: caption, name: caption,
id: neighborId, id: neighborId,
@@ -1660,7 +1668,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setState({ this.setState({
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map( possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => { (value: string): InputTypeaheadComponent.Item => {
return { caption: value, value: value }; return { caption: value, value: value };
} }
), ),
@@ -1681,20 +1689,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let updatedVertex = vertices[0]; const updatedVertex = vertices[0];
if (this.originalGraphData.hasVertexId(updatedVertex.id)) { if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id); const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
// Copy updated properties // Copy updated properties
if (currentVertex.hasOwnProperty("properties")) { if (Object.prototype.hasOwnProperty.call(currentVertex, "properties")) {
delete currentVertex["properties"]; delete currentVertex["properties"];
} }
for (var p in updatedVertex) { for (const p in updatedVertex) {
(currentVertex as any)[p] = updatedVertex[p]; (currentVertex as any)[p] = updatedVertex[p];
} }
} }
// TODO This kind of assumes saveVertexProperty is done from property panes. // TODO This kind of assumes saveVertexProperty is done from property panes.
let hn = this.state.highlightedNode; const hn = this.state.highlightedNode;
if (hn && hn.id === updatedVertex.id) { if (hn && hn.id === updatedVertex.id) {
this.updatePropertiesPane(hn.id); this.updatePropertiesPane(hn.id);
} }
@@ -1708,7 +1716,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
igraphConfig?: IGraphConfig igraphConfig?: IGraphConfig
) { ) {
this.originalGraphData = graphData; this.originalGraphData = graphData;
let gd = JSON.parse(JSON.stringify(this.originalGraphData)); const gd = JSON.parse(JSON.stringify(this.originalGraphData));
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet."); console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
return; return;
@@ -1873,7 +1881,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
promise promise
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result)) .then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
.catch((error: any) => { .catch((error: Error) => {
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`; const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg); GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({ this.setState({

View File

@@ -1,8 +1,8 @@
import * as React from "react"; import * as React from "react";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg"; import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
import ExpandIcon from "../../../../images/Expand_14x14.svg"; import ExpandIcon from "../../../../images/Expand_14x14.svg";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
interface MiddlePaneComponentProps { interface MiddlePaneComponentProps {
isTabsContentExpanded: boolean; isTabsContentExpanded: boolean;
@@ -17,7 +17,14 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
<div className="middlePane"> <div className="middlePane">
<div className="graphTitle"> <div className="graphTitle">
<span className="paneTitle">Graph</span> <span className="paneTitle">Graph</span>
<span className="graphExpandCollapseBtn pull-right" onClick={this.props.toggleExpandGraph}> <span
className="graphExpandCollapseBtn pull-right"
onClick={this.props.toggleExpandGraph}
role="button"
aria-expanded={this.props.isTabsContentExpanded}
aria-name="View graph in full screen"
tabIndex={0}
>
<img <img
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon} src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"} alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ import { useTabs } from "../../../hooks/useTabs";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { NotebookUtil } from "../../Notebook/NotebookUtil";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
@@ -55,15 +56,15 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor); const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if ( if (NotebookUtil.isPhoenixEnabled()) {
userContext.features.notebooksTemporarilyDown === false && uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
userContext.features.phoenix === true &&
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
) {
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
} }
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) { if (
userContext.features.phoenix === false &&
userContext.features.notebooksTemporarilyDown === false &&
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
) {
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker")); uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
} }

View File

@@ -307,11 +307,18 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
function createNewDatabase(container: Explorer): CommandButtonComponentProps { function createNewDatabase(container: Explorer): CommandButtonComponentProps {
const label = "New " + getDatabaseName(); const label = "New " + getDatabaseName();
const newDatabaseButton = document.activeElement as HTMLElement;
return { return {
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => onCommandClick: () =>
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />), useSidePanel
.getState()
.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel explorer={container} buttonElement={newDatabaseButton} />
),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
@@ -596,7 +603,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
return { return {
iconSrc: GitHubIcon, iconSrc: GitHubIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => onCommandClick: () => {
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
@@ -606,7 +613,8 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
gitHubClientProp={container.notebookManager.gitHubClient} gitHubClientProp={container.notebookManager.gitHubClient}
junoClientProp={junoClient} junoClientProp={junoClient}
/> />
), );
},
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: false, disabled: false,

View File

@@ -13,6 +13,7 @@ import { StyleConstants } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { ConnectionStatus } from "./ConnectionStatusComponent"; import { ConnectionStatus } from "./ConnectionStatusComponent";
import { MemoryTracker } from "./MemoryTrackerComponent"; import { MemoryTracker } from "./MemoryTrackerComponent";
@@ -203,9 +204,9 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
}; };
}; };
export const createConnectionStatus = (key: string): ICommandBarItemProps => { export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
return { return {
key, key,
onRender: () => <ConnectionStatus />, onRender: () => <ConnectionStatus container={container} />,
}; };
}; };

View File

@@ -3,77 +3,182 @@
.connectionStatusContainer { .connectionStatusContainer {
cursor: default; cursor: default;
align-items: center; align-items: center;
margin: 0 9px;
border: 1px; border: 1px;
min-height: 44px; min-height: 44px;
> span { > span {
padding-right: 12px; padding-right: 12px;
font-size: 13px; font-size: 12px;
font-family: @DataExplorerFont; font-family: @DataExplorerFont;
color: @DefaultFontColor; color: @DefaultFontColor;
} }
&:focus{
outline: 0px;
}
} }
.connectionStatusFailed{ .commandReactBtn {
color: #bd1919; &:hover {
background-color: rgb(238, 247, 255);
color: rgb(32, 31, 30);
cursor: pointer;
}
&:focus{
outline: 1px dashed #605e5c;
}
} }
.ring-container { .connectedReactBtn {
&:hover {
background-color: rgb(238, 247, 255);
color: rgb(32, 31, 30);
cursor: pointer;
}
&:focus{
outline: 0px;
}
}
.connectIcon{
margin: 0px 4px;
height: 18px;
width: 18px;
color: rgb(0, 120, 212);
}
.status {
position: relative; position: relative;
} display: block;
margin-right: 8px;
.ringringGreen { width: 1em;
border: 3px solid green; height: 1em;
border-radius: 30px; font-size: 9px!important;
height: 18px; padding: 0px!important;
width: 18px; border-radius: 0.5em;
}
.status::before,
.status::after {
position: absolute; position: absolute;
margin: .4285em 0em 0em 0.07477em; content: "";
animation: pulsate 3s ease-out; }
animation-iteration-count: infinite;
opacity: 0.0 .status::before {
} top: 0;
.ringringYellow{ left: 0;
border: 3px solid #ffbf00; width: 1em;
border-radius: 30px; height: 1em;
height: 18px; background-color: rgba(#fff, 0.1);
width: 18px; border-radius: 100%;
position: absolute; opacity: 1;
margin: .4285em 0em 0em 0.07477em; transform: translate3d(0, 0, 0) scale(0);
animation: pulsate 3s ease-out; }
animation-iteration-count: infinite;
opacity: 0.0 .connected{
} background-color: green;
.ringringRed{ box-shadow:
border: 3px solid #bd1919; 0 0 0 0em rgba(green, 0),
border-radius: 30px; 0em 0.05em 0.1em rgba(#000000, 0.2);
height: 18px; transform: translate3d(0, 0, 0) scale(1);
width: 18px; }
position: absolute; .connecting{
margin: .4285em 0em 0em 0.07477em; background-color:#ffbf00;
animation: pulsate 3s ease-out; box-shadow:
animation-iteration-count: infinite; 0 0 0 0em rgba(#ffbf00, 0),
opacity: 0.0 0em 0.05em 0.1em rgba(#000000, 0.2);
} transform: translate3d(0, 0, 0) scale(1);
@keyframes pulsate { }
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;} .failed{
15% {opacity: 0.8;} background-color:#bd1919;
25% {opacity: 0.6;} box-shadow:
45% {opacity: 0.4;} 0 0 0 0em rgba(#bd1919, 0),
70% {opacity: 0.3;} 0em 0.05em 0.1em rgba(#000000, 0.2);
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;} transform: translate3d(0, 0, 0) scale(1);
} }
.locationGreenDot{
font-size: 20px; .status.connecting.is-animating {
margin-right: 0.07em; animation: status-outer-connecting 3000ms infinite;
color: green; }
} .status.failed.is-animating {
.locationYellowDot{ animation: status-outer-failed 3000ms infinite;
font-size: 20px; }
margin-right: 0.07em; .status.connected.is-animating {
color: #ffbf00; animation: status-outer-connected 3000ms infinite;
} }
.locationRedDot{ @keyframes status-outer-connected {
font-size: 20px;
margin-right: 0.07em; 0% {
color: #bd1919; transform: translate3d(0, 0, 0) scale(1);
} box-shadow: 0 0 0 0em #008000, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
}
20% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.6), 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
}
40% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
}
60% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
}
80% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
}
85% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
}
}
@keyframes status-outer-failed {
0% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #bd1919, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
}
20% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #c52d2d, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
}
40% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #b47b7b, 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
}
60% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
}
80% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
}
85% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
}
}
@keyframes status-outer-connecting {
0% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #ffbf00, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
}
20% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #f0dfad, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
}
40% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(198, 243, 198, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
}
60% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(213, 241, 213, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
}
80% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
}
85% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
}
}

View File

@@ -1,17 +1,21 @@
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react"; import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
import { ActionButton } from "@fluentui/react/lib/Button";
import * as React from "react"; import * as React from "react";
import { ConnectionStatusType } from "../../../Common/Constants"; import "../../../../less/hostedexplorer.less";
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import "../CommandBar/ConnectionStatusComponent.less"; import "../CommandBar/ConnectionStatusComponent.less";
interface Props {
export const ConnectionStatus: React.FC = (): JSX.Element => { container: Explorer;
}
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
const [second, setSecond] = React.useState("00"); const [second, setSecond] = React.useState("00");
const [minute, setMinute] = React.useState("00"); const [minute, setMinute] = React.useState("00");
const [isActive, setIsActive] = React.useState(false); const [isActive, setIsActive] = React.useState(false);
const [counter, setCounter] = React.useState(0); const [counter, setCounter] = React.useState(0);
const [statusColor, setStatusColor] = React.useState("locationYellowDot"); const [statusColor, setStatusColor] = React.useState("");
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow"); const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
const toolTipContent = "Hosted runtime status.";
React.useEffect(() => { React.useEffect(() => {
let intervalId: NodeJS.Timeout; let intervalId: NodeJS.Timeout;
@@ -39,34 +43,65 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
}; };
const connectionInfo = useNotebook((state) => state.connectionInfo); const connectionInfo = useNotebook((state) => state.connectionInfo);
if (!connectionInfo) { const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
return <></>;
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
if (
connectionInfo &&
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
) {
return (
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
<TooltipHost content={toolTipContent}>
<Stack className="connectionStatusContainer" horizontal>
<Icon iconName="ConnectVirtualMachine" className="connectIcon" />
<span>{connectionInfo.status}</span>
</Stack>
</TooltipHost>
</ActionButton>
);
} }
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) { if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
setIsActive(true); setIsActive(true);
setStatusColor("status connecting is-animating");
setToolTipContent("Connecting to temporary workspace.");
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) { } else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
stopTimer(); stopTimer();
setStatusColor("locationGreenDot"); setStatusColor("status connected is-animating");
setStatusColorAnimation("ringringGreen"); setToolTipContent("Connected to temporary workspace.");
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) { } else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
stopTimer(); stopTimer();
setStatusColor("locationRedDot"); setStatusColor("status failed is-animating");
setStatusColorAnimation("ringringRed"); setToolTipContent("Click here to Reconnect to temporary workspace.");
} }
return ( return (
<TooltipHost content={toolTipContent}> <ActionButton
<Stack className="connectionStatusContainer" horizontal> className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
<div className="ring-container"> onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
<div className={statusColorAnimation}></div> connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
<Icon iconName="LocationDot" className={statusColor} /> }
</div> >
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}> <TooltipHost content={toolTipContent}>
{connectionInfo.status} <Stack className="connectionStatusContainer" horizontal>
</span> <i className={statusColor}></i>
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && ( <span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
<ProgressIndicator description={minute + ":" + second} /> {connectionInfo.status}
)} </span>
</Stack> {connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
</TooltipHost> <ProgressIndicator description={minute + ":" + second} />
)}
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
<ProgressIndicator
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
percentComplete={usedGB / totalGB}
/>
)}
</Stack>
</TooltipHost>
</ActionButton>
); );
}; };

View File

@@ -129,7 +129,7 @@ export class NotificationConsoleComponent extends React.Component<
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={"console button" + (this.props.isConsoleExpanded ? " collapsed" : " expanded")} aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")}
aria-expanded={!this.props.isConsoleExpanded} aria-expanded={!this.props.isConsoleExpanded}
> >
<img <img
@@ -205,7 +205,9 @@ export class NotificationConsoleComponent extends React.Component<
{item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />} {item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
{item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />} {item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />}
<span className="date">{item.date}</span> <span className="date">{item.date}</span>
<span className="message">{item.message}</span> <span className="message" role="alert" aria-live="assertive">
{item.message}
</span>
</div> </div>
)); ));
} }

View File

@@ -70,7 +70,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
</div> </div>
<div <div
aria-expanded={true} aria-expanded={true}
aria-label="console button expanded" aria-label="console button collapsed"
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -236,7 +236,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
</div> </div>
<div <div
aria-expanded={true} aria-expanded={true}
aria-label="console button expanded" aria-label="console button collapsed"
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -340,7 +340,9 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
date date
</span> </span>
<span <span
aria-live="assertive"
className="message" className="message"
role="alert"
> >
message message
</span> </span>

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable"; import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
// Vendor modules // Vendor modules
import { import {
@@ -30,6 +31,19 @@ export interface NotebookComponentBootstrapperOptions {
contentRef: ContentRef; contentRef: ContentRef;
} }
interface IWrapModel {
name: string;
path: string;
last_modified: Date;
created: string;
content: unknown;
format: string;
mimetype: unknown;
size: number;
writeable: boolean;
type: string;
}
export class NotebookComponentBootstrapper { export class NotebookComponentBootstrapper {
public contentRef: ContentRef; public contentRef: ContentRef;
protected renderExtraComponent: () => JSX.Element; protected renderExtraComponent: () => JSX.Element;
@@ -41,7 +55,7 @@ export class NotebookComponentBootstrapper {
this.contentRef = options.contentRef; this.contentRef = options.contentRef;
} }
protected static wrapModelIntoContent(name: string, path: string, content: any) { protected static wrapModelIntoContent(name: string, path: string, content: unknown): IWrapModel {
return { return {
name, name,
path, path,
@@ -49,7 +63,7 @@ export class NotebookComponentBootstrapper {
created: "", created: "",
content, content,
format: "json", format: "json",
mimetype: null as any, mimetype: undefined,
size: 0, size: 0,
writeable: false, writeable: false,
type: "notebook", type: "notebook",
@@ -85,7 +99,7 @@ export class NotebookComponentBootstrapper {
}; };
} }
public setContent(name: string, content: any): void { public setContent(name: string, content: unknown): void {
this.getStore().dispatch( this.getStore().dispatch(
actions.fetchContentFulfilled({ actions.fetchContentFulfilled({
filepath: undefined, filepath: undefined,
@@ -270,7 +284,6 @@ export class NotebookComponentBootstrapper {
public isContentDirty(): boolean { public isContentDirty(): boolean {
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef }); const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
if (!content) { if (!content) {
console.log("No error");
return false; return false;
} }

View File

@@ -2,12 +2,15 @@
* Notebook container related stuff * Notebook container related stuff
*/ */
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { ConnectionStatusType } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { NotebookUtil } from "./NotebookUtil";
import { useNotebook } from "./useNotebook"; import { useNotebook } from "./useNotebook";
export class NotebookContainerClient { export class NotebookContainerClient {
@@ -42,7 +45,7 @@ export class NotebookContainerClient {
}, delayMs); }, delayMs);
} }
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> { public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
const notebookServerInfo = useNotebook.getState().notebookServerInfo; const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) { if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
const error = "No server endpoint detected"; const error = "No server endpoint detected";
@@ -75,6 +78,12 @@ export class NotebookContainerClient {
freeKB: memoryUsageInfo.free, freeKB: memoryUsageInfo.free,
}; };
} }
} else if (NotebookUtil.isPhoenixEnabled()) {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.ReConnect,
};
useNotebook.getState().resetConatinerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(true);
} }
return undefined; return undefined;
} catch (error) { } catch (error) {
@@ -84,6 +93,13 @@ export class NotebookContainerClient {
"Connection lost with Notebook server. Attempting to reconnect..." "Connection lost with Notebook server. Attempting to reconnect..."
); );
} }
if (NotebookUtil.isPhoenixEnabled()) {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Failed,
};
useNotebook.getState().resetConatinerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(true);
}
this.onConnectionLost(); this.onConnectionLost();
return undefined; return undefined;
} }

View File

@@ -212,6 +212,7 @@ export default class NotebookManager {
"Cancel", "Cancel",
() => reject(new Error("Commit dialog canceled")), () => reject(new Error("Commit dialog canceled")),
undefined, undefined,
undefined,
{ {
label: "Commit message", label: "Commit message",
autoAdjustHeight: true, autoAdjustHeight: true,

View File

@@ -16,9 +16,10 @@ import "./NotebookReadOnlyRenderer.less";
import SandboxOutputs from "./outputs/SandboxOutputs"; import SandboxOutputs from "./outputs/SandboxOutputs";
export interface NotebookRendererProps { export interface NotebookRendererProps {
contentRef: any; contentRef: ContentRef;
hideInputs?: boolean; hideInputs?: boolean;
hidePrompts?: boolean; hidePrompts?: boolean;
addTransform: (component: React.ComponentType & { MIMETYPE: string }) => void;
} }
/** /**
@@ -27,7 +28,7 @@ export interface NotebookRendererProps {
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> { class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
componentDidMount() { componentDidMount() {
if (!userContext.features.sandboxNotebookOutputs) { if (!userContext.features.sandboxNotebookOutputs) {
loadTransform(this.props as any); loadTransform(this.props as NotebookRendererProps);
} }
} }
@@ -59,7 +60,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
<div className="NotebookReadOnlyRender"> <div className="NotebookReadOnlyRender">
<Cells contentRef={this.props.contentRef}> <Cells contentRef={this.props.contentRef}>
{{ {{
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => ( code: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
<CodeCell id={id} contentRef={contentRef}> <CodeCell id={id} contentRef={contentRef}>
{{ {{
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef), prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
@@ -73,14 +74,14 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
}} }}
</CodeCell> </CodeCell>
), ),
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => ( markdown: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown"> <MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
{{ {{
editor: {}, editor: {},
}} }}
</MarkdownCell> </MarkdownCell>
), ),
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => ( raw: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
<RawCell id={id} contentRef={contentRef} cell_type="raw"> <RawCell id={id} contentRef={contentRef} cell_type="raw">
{{ {{
editor: { editor: {
@@ -98,6 +99,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => { const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
const mapDispatchToProps = (dispatch: Dispatch) => { const mapDispatchToProps = (dispatch: Dispatch) => {
return { return {
@@ -114,4 +116,4 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
return mapDispatchToProps; return mapDispatchToProps;
}; };
export default connect(null, makeMapDispatchToProps)(NotebookReadOnlyRenderer); export default connect(undefined, makeMapDispatchToProps)(NotebookReadOnlyRenderer);

View File

@@ -3,6 +3,7 @@ import { AppState, selectors } from "@nteract/core";
import domtoimage from "dom-to-image"; import domtoimage from "dom-to-image";
import Html2Canvas from "html2canvas"; import Html2Canvas from "html2canvas";
import path from "path"; import path from "path";
import { userContext } from "../../UserContext";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as StringUtils from "../../Utils/StringUtils"; import * as StringUtils from "../../Utils/StringUtils";
import { SnapshotFragment } from "./NotebookComponent/types"; import { SnapshotFragment } from "./NotebookComponent/types";
@@ -328,4 +329,16 @@ export class NotebookUtil {
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
} }
public static getNotebookBtnTitle(fileName: string): string {
if (this.isPhoenixEnabled()) {
return `Download to ${fileName}`;
} else {
return `Download to my notebooks`;
}
}
public static isPhoenixEnabled(): boolean {
return userContext.features.notebooksTemporarilyDown === false && userContext.features.phoenix === true;
}
} }

View File

@@ -2,10 +2,12 @@ import { cloneDeep } from "lodash";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { ConnectionStatusType } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
import { IPinnedRepo } from "../../Juno/JunoClient"; import { IPinnedRepo } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -14,6 +16,7 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
import NotebookManager from "./NotebookManager"; import NotebookManager from "./NotebookManager";
import { NotebookUtil } from "./NotebookUtil";
interface NotebookState { interface NotebookState {
isNotebookEnabled: boolean; isNotebookEnabled: boolean;
@@ -28,8 +31,10 @@ interface NotebookState {
myNotebooksContentRoot: NotebookContentItem; myNotebooksContentRoot: NotebookContentItem;
gitHubNotebooksContentRoot: NotebookContentItem; gitHubNotebooksContentRoot: NotebookContentItem;
galleryContentRoot: NotebookContentItem; galleryContentRoot: NotebookContentItem;
connectionInfo: DataModels.ContainerConnectionInfo; connectionInfo: ContainerConnectionInfo;
notebookFolderName: string; notebookFolderName: string;
isAllocating: boolean;
isRefreshed: boolean;
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void; setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void; setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void; setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
@@ -46,7 +51,10 @@ interface NotebookState {
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void; deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>; initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void; initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void; setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
setIsAllocating: (isAllocating: boolean) => void;
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
setIsRefreshed: (isAllocating: boolean) => void;
} }
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
@@ -69,8 +77,12 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
myNotebooksContentRoot: undefined, myNotebooksContentRoot: undefined,
gitHubNotebooksContentRoot: undefined, gitHubNotebooksContentRoot: undefined,
galleryContentRoot: undefined, galleryContentRoot: undefined,
connectionInfo: undefined, connectionInfo: {
status: ConnectionStatusType.Connect,
},
notebookFolderName: undefined, notebookFolderName: undefined,
isAllocating: false,
isRefreshed: false,
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }), setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }), setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
@@ -175,7 +187,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root }); isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
}, },
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => { initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks"; const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
set({ notebookFolderName }); set({ notebookFolderName });
const myNotebooksContentRoot = { const myNotebooksContentRoot = {
name: get().notebookFolderName, name: get().notebookFolderName,
@@ -256,5 +268,15 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
set({ gitHubNotebooksContentRoot }); set({ gitHubNotebooksContentRoot });
} }
}, },
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }), setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: undefined,
authToken: undefined,
});
useNotebook.getState().setIsAllocating(false);
},
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
})); }));

View File

@@ -13,21 +13,21 @@ import {
Text, Text,
TooltipHost, TooltipHost,
} from "@fluentui/react"; } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { createCollection } from "Common/dataAccess/createCollection";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels";
import { SubscriptionType } from "Contracts/SubscriptionType";
import { useSidePanel } from "hooks/useSidePanel";
import React from "react"; import React from "react";
import * as Constants from "../../Common/Constants"; import { CollectionCreation } from "Shared/Constants";
import { createCollection } from "../../Common/dataAccess/createCollection"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { configContext, Platform } from "../../ConfigContext"; import { userContext } from "UserContext";
import * as DataModels from "../../Contracts/DataModels"; import { getCollectionName } from "Utils/APITypeUtils";
import { SubscriptionType } from "../../Contracts/SubscriptionType"; import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils";
import { useSidePanel } from "../../hooks/useSidePanel"; import { getUpsellMessage } from "Utils/PricingUtils";
import { CollectionCreation } from "../../Shared/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils";
import { isCapabilityEnabled, isServerlessAccount } from "../../Utils/CapabilityUtils";
import { getUpsellMessage } from "../../Utils/PricingUtils";
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
@@ -999,7 +999,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
const collectionId: string = this.state.collectionId.trim(); const collectionId: string = this.state.collectionId.trim();
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId; let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
let partitionKeyString = this.state.partitionKey.trim(); let partitionKeyString = this.state.isSharded ? this.state.partitionKey.trim() : undefined;
if (userContext.apiType === "Tables") { if (userContext.apiType === "Tables") {
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk' // Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'

View File

@@ -23,10 +23,12 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
export interface AddDatabasePaneProps { export interface AddDatabasePaneProps {
explorer: Explorer; explorer: Explorer;
buttonElement?: HTMLElement;
} }
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
explorer: container, explorer: container,
buttonElement,
}: AddDatabasePaneProps) => { }: AddDatabasePaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
let throughput: number; let throughput: number;
@@ -77,6 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
}; };
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage); TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
buttonElement.focus();
}, []); }, []);
const onSubmit = () => { const onSubmit = () => {

View File

@@ -1,14 +1,14 @@
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react"; import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import * as Constants from "../../../Common/Constants"; import * as SharedConstants from "Shared/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { userContext } from "UserContext";
import * as SharedConstants from "../../../Shared/Constants"; import { isServerlessAccount } from "Utils/CapabilityUtils";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
@@ -198,6 +198,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<TextField <TextField
aria-required="true" aria-required="true"
required={true}
autoComplete="off" autoComplete="off"
styles={getTextFieldStyles()} styles={getTextFieldStyles()}
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"
@@ -285,6 +286,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
underlined underlined
styles={getTextFieldStyles({ fontSize: 12, width: 150 })} styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
aria-required="true" aria-required="true"
required={true}
ariaLabel="addCollection-tableId" ariaLabel="addCollection-tableId"
autoComplete="off" autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"

View File

@@ -77,7 +77,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
selectedLocation.repo selectedLocation.repo
)} - ${selectedLocation.branch}`; )} - ${selectedLocation.branch}`;
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) { } else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
destination = "My Notebooks Scratch"; destination = useNotebook.getState().notebookFolderName;
} }
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`); clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);

View File

@@ -1,18 +1,18 @@
import { Text, TextField } from "@fluentui/react"; import { Text, TextField } from "@fluentui/react";
import { Areas } from "Common/Constants";
import { deleteCollection } from "Common/dataAccess/deleteCollection";
import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { Collection } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { Areas } from "../../../Common/Constants"; import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import DeleteFeedback from "../../../Common/DeleteFeedback"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { userContext } from "UserContext";
import { Collection } from "../../../Contracts/ViewModels"; import { getCollectionName } from "Utils/APITypeUtils";
import { useSidePanel } from "../../../hooks/useSidePanel"; import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import { useTabs } from "../../../hooks/useTabs";
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";

View File

@@ -1,18 +1,18 @@
import { Text, TextField } from "@fluentui/react"; import { Text, TextField } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import { Areas } from "Common/Constants";
import { deleteDatabase } from "Common/dataAccess/deleteDatabase";
import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { Collection, Database } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { Areas } from "../../Common/Constants"; import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import DeleteFeedback from "../../Common/DeleteFeedback"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { userContext } from "UserContext";
import { Collection, Database } from "../../Contracts/ViewModels"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "../../hooks/useSidePanel";
import { useTabs } from "../../hooks/useTabs";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent"; import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";

View File

@@ -9,6 +9,7 @@
flex-grow: 1; flex-grow: 1;
padding: 0 34px; padding: 0 34px;
margin: 20px 0; margin: 20px 0;
overflow-x: hidden;
& > :not(.collapsibleSection) { & > :not(.collapsibleSection) {
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;

View File

@@ -33,7 +33,13 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center"> <Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
{icon} {icon}
<span className="panelWarningErrorDetailsLinkContainer"> <span className="panelWarningErrorDetailsLinkContainer">
<Text className="panelWarningErrorMessage" variant="small" aria-label="message"> <Text
role="alert"
aria-live="assertive"
aria-label={message}
className="panelWarningErrorMessage"
variant="small"
>
{message} {message}
{link && linkText && ( {link && linkText && (
<Link target="_blank" href={link}> <Link target="_blank" href={link}>

View File

@@ -34,6 +34,6 @@ describe("Right Pane Form", () => {
it("should render error in header", () => { it("should render error in header", () => {
render(<RightPaneForm {...props} formError="file already Exist" />); render(<RightPaneForm {...props} formError="file already Exist" />);
expect(screen.getByLabelText("error")).toBeDefined(); expect(screen.getByLabelText("error")).toBeDefined();
expect(screen.getByLabelText("message").innerHTML).toEqual("file already Exist"); expect(screen.getByRole("alert").innerHTML).toEqual("file already Exist");
}); });
}); });

View File

@@ -1,13 +1,13 @@
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react"; import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { configContext } from "ConfigContext";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, MouseEvent, useState } from "react"; import React, { FunctionComponent, MouseEvent, useState } from "react";
import * as Constants from "../../../Common/Constants"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import * as StringUtility from "Shared/StringUtility";
import { configContext } from "../../../ConfigContext"; import { userContext } from "UserContext";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
import * as StringUtility from "../../../Shared/StringUtility";
import { userContext } from "../../../UserContext";
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export const SettingsPane: FunctionComponent = () => { export const SettingsPane: FunctionComponent = () => {

View File

@@ -1,8 +1,8 @@
import { TextField } from "@fluentui/react"; import { TextField } from "@fluentui/react";
import * as ViewModels from "Contracts/ViewModels";
import { useTabs } from "hooks/useTabs";
import React, { FormEvent, FunctionComponent, useState } from "react"; import React, { FormEvent, FunctionComponent, useState } from "react";
import * as ViewModels from "../../../Contracts/ViewModels"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
import { useTabs } from "../../../hooks/useTabs";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import * as FileSystemUtil from "../../Notebook/FileSystemUtil"; import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import NotebookV2Tab from "../../Tabs/NotebookV2Tab"; import NotebookV2Tab from "../../Tabs/NotebookV2Tab";

View File

@@ -29,6 +29,7 @@ describe("Table query select Panel", () => {
it("Should checked availableCheckbox by default", () => { it("Should checked availableCheckbox by default", () => {
const wrapper = mount(<TableQuerySelectPanel {...props} />); const wrapper = mount(<TableQuerySelectPanel {...props} />);
expect(wrapper.find("#availableCheckbox").first().props()).toEqual({ expect(wrapper.find("#availableCheckbox").first().props()).toEqual({
ariaPositionInSet: 0,
id: "availableCheckbox", id: "availableCheckbox",
label: "Available Columns", label: "Available Columns",
checked: true, checked: true,

View File

@@ -128,8 +128,9 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
label="Available Columns" label="Available Columns"
checked={isAvailableColumnChecked} checked={isAvailableColumnChecked}
onChange={availableColumnsCheckboxClick} onChange={availableColumnsCheckboxClick}
ariaPositionInSet={0}
/> />
{columnOptions.map((column) => { {columnOptions.map((column, index) => {
return ( return (
<Checkbox <Checkbox
label={column.columnName} label={column.columnName}
@@ -137,6 +138,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
key={column.columnName} key={column.columnName}
checked={column.selected} checked={column.selected}
disabled={!column.editable} disabled={!column.editable}
ariaPositionInSet={index + 1}
/> />
); );
})} })}

View File

@@ -37,12 +37,14 @@ exports[`Table query select Panel should render Default properly 1`] = `
className="column-select-view" className="column-select-view"
> >
<StyledCheckboxBase <StyledCheckboxBase
ariaPositionInSet={0}
checked={true} checked={true}
id="availableCheckbox" id="availableCheckbox"
label="Available Columns" label="Available Columns"
onChange={[Function]} onChange={[Function]}
> >
<CheckboxBase <CheckboxBase
ariaPositionInSet={0}
checked={true} checked={true}
id="availableCheckbox" id="availableCheckbox"
label="Available Columns" label="Available Columns"
@@ -328,6 +330,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
<input <input
aria-checked="true" aria-checked="true"
aria-label="Available Columns" aria-label="Available Columns"
aria-posinset={0}
checked={true} checked={true}
className="input-55" className="input-55"
data-ktp-execute-target={true} data-ktp-execute-target={true}
@@ -646,6 +649,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
</CheckboxBase> </CheckboxBase>
</StyledCheckboxBase> </StyledCheckboxBase>
<StyledCheckboxBase <StyledCheckboxBase
ariaPositionInSet={1}
checked={true} checked={true}
disabled={false} disabled={false}
key="" key=""
@@ -653,6 +657,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
onChange={[Function]} onChange={[Function]}
> >
<CheckboxBase <CheckboxBase
ariaPositionInSet={1}
checked={true} checked={true}
disabled={false} disabled={false}
label="" label=""
@@ -939,6 +944,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
aria-checked="true" aria-checked="true"
aria-disabled={false} aria-disabled={false}
aria-label="" aria-label=""
aria-posinset={1}
checked={true} checked={true}
className="input-55" className="input-55"
data-ktp-execute-target={true} data-ktp-execute-target={true}

View File

@@ -1,7 +1,7 @@
import { Upload } from "Common/Upload/Upload";
import { useSidePanel } from "hooks/useSidePanel";
import React, { ChangeEvent, FunctionComponent, useState } from "react"; import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";

View File

@@ -1,8 +1,8 @@
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@fluentui/react"; import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@fluentui/react";
import { Upload } from "Common/Upload/Upload";
import { UploadDetailsRecord } from "Contracts/ViewModels";
import React, { ChangeEvent, FunctionComponent, useState } from "react"; import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import { getErrorMessage } from "../../Tables/Utilities"; import { getErrorMessage } from "../../Tables/Utilities";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";

View File

@@ -327,13 +327,17 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
key=".0:$.1" key=".0:$.1"
> >
<Text <Text
aria-label="message" aria-label="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources."
aria-live="assertive"
className="panelWarningErrorMessage" className="panelWarningErrorMessage"
role="alert"
variant="small" variant="small"
> >
<span <span
aria-label="message" aria-label="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources."
aria-live="assertive"
className="panelWarningErrorMessage css-56" className="panelWarningErrorMessage css-56"
role="alert"
> >
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources. Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
</span> </span>

View File

@@ -307,16 +307,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
title: "New " + getDatabaseName(), title: "New " + getDatabaseName(),
description: undefined, description: undefined,
onClick: () => onClick: () => this.openAddDatabasePanel(),
useSidePanel
.getState()
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
}); });
} }
return items; return items;
} }
private openAddDatabasePanel() {
const newDatabaseButton = document.activeElement as HTMLElement;
useSidePanel
.getState()
.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
);
}
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) { private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
return { return {
iconSrc: NotebookIcon, iconSrc: NotebookIcon,

View File

@@ -1,12 +1,11 @@
import _ from "underscore";
import Q from "q"; import Q from "q";
import _ from "underscore";
import * as Entities from "../Entities";
import * as QueryBuilderConstants from "../Constants"; import * as QueryBuilderConstants from "../Constants";
import * as Entities from "../Entities";
import * as Utilities from "../Utilities"; import * as Utilities from "../Utilities";
export function getRowSelector(selectorSchema: Entities.IProperty[]): string { export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
var selector: string = ""; let selector = "";
selectorSchema && selectorSchema &&
selectorSchema.forEach((p: Entities.IProperty) => { selectorSchema.forEach((p: Entities.IProperty) => {
selector += "[" + p.key + '="' + Utilities.jQuerySelectorEscape(p.value) + '"]'; selector += "[" + p.key + '="' + Utilities.jQuerySelectorEscape(p.value) + '"]';
@@ -15,10 +14,10 @@ export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
} }
export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElement): boolean { export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElement): boolean {
var isVisible = false; let isVisible = false;
if (dataTableScrollBodyQuery.length && element) { if (dataTableScrollBodyQuery.length && element) {
var elementRect: ClientRect = element.getBoundingClientRect(), const elementRect: ClientRect = element.getBoundingClientRect(),
dataTableScrollBodyRect: ClientRect = dataTableScrollBodyQuery.get(0).getBoundingClientRect(); dataTableScrollBodyRect: ClientRect = dataTableScrollBodyQuery.get(0).getBoundingClientRect();
isVisible = elementRect.bottom <= dataTableScrollBodyRect.bottom && dataTableScrollBodyRect.top <= elementRect.top; isVisible = elementRect.bottom <= dataTableScrollBodyRect.bottom && dataTableScrollBodyRect.top <= elementRect.top;
@@ -29,17 +28,17 @@ export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElem
export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number, isScrollUp: boolean): void { export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number, isScrollUp: boolean): void {
if (dataTableRows.length) { if (dataTableRows.length) {
var dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector), const dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector),
selectedRowElement: HTMLElement = dataTableRows.get(currentIndex); selectedRowElement: HTMLElement = dataTableRows.get(currentIndex);
if (dataTableScrollBodyQuery.length && selectedRowElement) { if (dataTableScrollBodyQuery.length && selectedRowElement) {
var isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement); const isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement);
if (!isVisible) { if (!isVisible) {
var selectedRowQuery: JQuery = $(selectedRowElement), const selectedRowQuery: JQuery = $(selectedRowElement),
scrollPosition: number = dataTableScrollBodyQuery.scrollTop(), scrollPosition: number = dataTableScrollBodyQuery.scrollTop(),
selectedElementPosition: number = selectedRowQuery.position().top, selectedElementPosition: number = selectedRowQuery.position().top;
newScrollPosition: number = 0; let newScrollPosition = 0;
if (isScrollUp) { if (isScrollUp) {
newScrollPosition = scrollPosition + selectedElementPosition; newScrollPosition = scrollPosition + selectedElementPosition;
@@ -55,7 +54,7 @@ export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number,
} }
export function scrollToTopIfNeeded(): void { export function scrollToTopIfNeeded(): void {
var $dataTableRows: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector), const $dataTableRows: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector),
$dataTableScrollBody: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector); $dataTableScrollBody: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector);
if ($dataTableRows.length && $dataTableScrollBody.length) { if ($dataTableRows.length && $dataTableScrollBody.length) {
@@ -88,13 +87,14 @@ export function reorderColumns(
table: DataTables.DataTable, table: DataTables.DataTable,
targetOrder: number[], targetOrder: number[],
currentOrder?: number[] currentOrder?: number[]
//eslint-disable-next-line
): Q.Promise<any> { ): Q.Promise<any> {
var columnsCount: number = targetOrder.length; const columnsCount: number = targetOrder.length;
var isCurrentOrderPassedIn: boolean = !!currentOrder; const isCurrentOrderPassedIn = !!currentOrder;
if (!isCurrentOrderPassedIn) { if (!isCurrentOrderPassedIn) {
currentOrder = getInitialOrder(columnsCount); currentOrder = getInitialOrder(columnsCount);
} }
var isSameOrder: boolean = Utilities.isEqual(currentOrder, targetOrder); const isSameOrder: boolean = Utilities.isEqual(currentOrder, targetOrder);
// if the targetOrder is the same as current order, do nothing. // if the targetOrder is the same as current order, do nothing.
if (!isSameOrder) { if (!isSameOrder) {
@@ -104,7 +104,7 @@ export function reorderColumns(
// Then the transformation order will be the same as target order. // Then the transformation order will be the same as target order.
// If current order is specified, then a transformation order is calculated. // If current order is specified, then a transformation order is calculated.
// Refer to calculateTransformationOrder for details about transformation order. // Refer to calculateTransformationOrder for details about transformation order.
var transformationOrder: number[] = isCurrentOrderPassedIn const transformationOrder: number[] = isCurrentOrderPassedIn
? calculateTransformationOrder(currentOrder, targetOrder) ? calculateTransformationOrder(currentOrder, targetOrder)
: targetOrder; : targetOrder;
try { try {
@@ -143,7 +143,7 @@ export function getCurrentOrder(table: DataTables.DataTable): number[] {
* Result: [0, 1, 2, 5, 6, 7, 3, 4, 8] * Result: [0, 1, 2, 5, 6, 7, 3, 4, 8]
*/ */
export function invertIndexValues(inputArray: number[]): number[] { export function invertIndexValues(inputArray: number[]): number[] {
var invertedArray: number[] = []; const invertedArray: number[] = [];
if (inputArray) { if (inputArray) {
inputArray.forEach((value: number, index: number) => { inputArray.forEach((value: number, index: number) => {
invertedArray[inputArray[index]] = index; invertedArray[inputArray[index]] = index;
@@ -170,20 +170,21 @@ export function invertIndexValues(inputArray: number[]): number[] {
* transformation order: Trans = [0, 1, 2, 7, 3, 4, 8, 5, 6] * transformation order: Trans = [0, 1, 2, 7, 3, 4, 8, 5, 6]
*/ */
export function calculateTransformationOrder(currentOrder: number[], targetOrder: number[]): number[] { export function calculateTransformationOrder(currentOrder: number[], targetOrder: number[]): number[] {
var transformationOrder: number[] = []; let transformationOrder: number[] = [];
if (currentOrder && targetOrder && currentOrder.length === targetOrder.length) { if (currentOrder && targetOrder && currentOrder.length === targetOrder.length) {
var invertedCurrentOrder: number[] = invertIndexValues(currentOrder); const invertedCurrentOrder: number[] = invertIndexValues(currentOrder);
transformationOrder = targetOrder.map((value: number) => invertedCurrentOrder[value]); transformationOrder = targetOrder.map((value: number) => invertedCurrentOrder[value]);
} }
return transformationOrder; return transformationOrder;
} }
export function getDataTableHeaders(table: DataTables.DataTable): string[] { export function getDataTableHeaders(table: DataTables.DataTable): string[] {
var columns: DataTables.ColumnsMethods = table.columns(); const columns: DataTables.ColumnsMethods = table.columns();
var headers: string[] = []; let headers: string[] = [];
if (columns) { if (columns) {
// table.columns() return ColumnsMethods which is an array of arrays // table.columns() return ColumnsMethods which is an array of arrays
var columnIndexes: number[] = (<any>columns)[0]; //eslint-disable-next-line
const columnIndexes: number[] = (<any>columns)[0];
if (columnIndexes) { if (columnIndexes) {
headers = columnIndexes.map((value: number) => $(table.columns(value).header()).html()); headers = columnIndexes.map((value: number) => $(table.columns(value).header()).html());
} }

View File

@@ -8,11 +8,11 @@ import TableEntityListViewModel from "./TableEntityListViewModel";
export default class TableCommands { export default class TableCommands {
// Command Ids // Command Ids
public static editEntityCommand: string = "edit"; public static editEntityCommand = "edit";
public static deleteEntitiesCommand: string = "delete"; public static deleteEntitiesCommand = "delete";
public static reorderColumnsCommand: string = "reorder"; public static reorderColumnsCommand = "reorder";
public static resetColumnsCommand: string = "reset"; public static resetColumnsCommand = "reset";
public static customizeColumnsCommand: string = "customizeColumns"; public static customizeColumnsCommand = "customizeColumns";
private _container: Explorer; private _container: Explorer;
@@ -21,8 +21,8 @@ export default class TableCommands {
} }
public isEnabled(commandName: string, selectedEntites: Entities.ITableEntity[]): boolean { public isEnabled(commandName: string, selectedEntites: Entities.ITableEntity[]): boolean {
var singleItemSelected: boolean = DataTableUtilities.containSingleItem(selectedEntites); const singleItemSelected = DataTableUtilities.containSingleItem(selectedEntites);
var atLeastOneItemSelected: boolean = DataTableUtilities.containItems(selectedEntites); const atLeastOneItemSelected = DataTableUtilities.containItems(selectedEntites);
switch (commandName) { switch (commandName) {
case TableCommands.editEntityCommand: case TableCommands.editEntityCommand:
return singleItemSelected; return singleItemSelected;
@@ -47,6 +47,7 @@ export default class TableCommands {
/** /**
* Edit entity * Edit entity
*/ */
//eslint-disable-next-line
public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
if (!viewModel) { if (!viewModel) {
return null; // Error return null; // Error
@@ -56,12 +57,9 @@ export default class TableCommands {
return null; // Erorr return null; // Erorr
} }
var entityToUpdate: Entities.ITableEntity = viewModel.selected()[0];
var originalNumberOfProperties = entityToUpdate ? 0 : Object.keys(entityToUpdate).length - 1; // .metadata is always a property for etag
return null; return null;
} }
//eslint-disable-next-line
public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
if (!viewModel) { if (!viewModel) {
return null; // Error return null; // Error
@@ -69,7 +67,7 @@ export default class TableCommands {
if (!DataTableUtilities.containItems(viewModel.selected())) { if (!DataTableUtilities.containItems(viewModel.selected())) {
return null; // Error return null; // Error
} }
var entitiesToDelete: Entities.ITableEntity[] = viewModel.selected(); const entitiesToDelete: Entities.ITableEntity[] = viewModel.selected();
const deleteMessage: string = const deleteMessage: string =
userContext.apiType === "Cassandra" userContext.apiType === "Cassandra"
? "Are you sure you want to delete the selected rows?" ? "Are you sure you want to delete the selected rows?"
@@ -82,7 +80,7 @@ export default class TableCommands {
() => { () => {
viewModel.queryTablesTab.container.tableDataClient viewModel.queryTablesTab.container.tableDataClient
.deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete) .deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete)
.then((results: any) => { .then(() => {
return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => { return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => {
viewModel.redrawTableThrottled(); viewModel.redrawTableThrottled();
}); });

View File

@@ -1,5 +1,5 @@
import * as Utilities from "../Utilities";
import * as Entities from "../Entities"; import * as Entities from "../Entities";
import * as Utilities from "../Utilities";
import CacheBase from "./CacheBase"; import CacheBase from "./CacheBase";
export default class TableEntityCache extends CacheBase<Entities.ITableEntity> { export default class TableEntityCache extends CacheBase<Entities.ITableEntity> {
@@ -21,7 +21,7 @@ export default class TableEntityCache extends CacheBase<Entities.ITableEntity> {
this._tableQuery = Utilities.copyTableQuery(tableQuery); this._tableQuery = Utilities.copyTableQuery(tableQuery);
} }
public preClear() { public preClear(): void {
this.tableQuery = null; this.tableQuery = null;
} }
} }

View File

@@ -1,4 +1,4 @@
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos"; import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
export interface ITableEntity { export interface ITableEntity {
[property: string]: ITableEntityAttribute; [property: string]: ITableEntityAttribute;
@@ -17,6 +17,7 @@ export interface ITableEntityAttribute {
export interface IListTableEntitiesResult { export interface IListTableEntitiesResult {
Results: ITableEntity[]; Results: ITableEntity[];
//eslint-disable-next-line
ContinuationToken: any; ContinuationToken: any;
iterator?: QueryIterator<ItemDefinition & Resource>; iterator?: QueryIterator<ItemDefinition & Resource>;
} }

View File

@@ -1,8 +1,9 @@
import QueryClauseViewModel from "./QueryClauseViewModel";
import * as Utilities from "../Utilities"; import * as Utilities from "../Utilities";
import QueryClauseViewModel from "./QueryClauseViewModel";
export default class ClauseGroup { export default class ClauseGroup {
public isRootGroup: boolean; public isRootGroup: boolean;
//eslint-disable-next-line
public children = new Array(); public children = new Array();
public parentGroup: ClauseGroup; public parentGroup: ClauseGroup;
private _id: string; private _id: string;
@@ -17,7 +18,7 @@ export default class ClauseGroup {
* Flattens the clause tree into an array, depth-first, left to right. * Flattens the clause tree into an array, depth-first, left to right.
*/ */
public flattenClauses(targetArray: ko.ObservableArray<QueryClauseViewModel>): void { public flattenClauses(targetArray: ko.ObservableArray<QueryClauseViewModel>): void {
var tempArray = new Array<QueryClauseViewModel>(); const tempArray = new Array<QueryClauseViewModel>();
this.flattenClausesImpl(this, tempArray); this.flattenClausesImpl(this, tempArray);
targetArray.removeAll(); targetArray.removeAll();
@@ -31,10 +32,10 @@ export default class ClauseGroup {
newClause.clauseGroup = this; newClause.clauseGroup = this;
this.children.push(newClause); this.children.push(newClause);
} else { } else {
var targetGroup = insertBefore.clauseGroup; const targetGroup = insertBefore.clauseGroup;
if (targetGroup) { if (targetGroup) {
var insertBeforeIndex = targetGroup.children.indexOf(insertBefore); const insertBeforeIndex = targetGroup.children.indexOf(insertBefore);
newClause.clauseGroup = targetGroup; newClause.clauseGroup = targetGroup;
targetGroup.children.splice(insertBeforeIndex, 0, newClause); targetGroup.children.splice(insertBeforeIndex, 0, newClause);
} }
@@ -42,19 +43,19 @@ export default class ClauseGroup {
} }
public deleteClause(clause: QueryClauseViewModel): void { public deleteClause(clause: QueryClauseViewModel): void {
var targetGroup = clause.clauseGroup; const targetGroup = clause.clauseGroup;
if (targetGroup) { if (targetGroup) {
var index = targetGroup.children.indexOf(clause); const index = targetGroup.children.indexOf(clause);
targetGroup.children.splice(index, 1); targetGroup.children.splice(index, 1);
clause.dispose(); clause.dispose();
if (targetGroup.children.length <= 1 && !targetGroup.isRootGroup) { if (targetGroup.children.length <= 1 && !targetGroup.isRootGroup) {
var parent = targetGroup.parentGroup; const parent = targetGroup.parentGroup;
var targetGroupIndex = parent.children.indexOf(targetGroup); const targetGroupIndex = parent.children.indexOf(targetGroup);
if (targetGroup.children.length === 1) { if (targetGroup.children.length === 1) {
var orphan = targetGroup.children.shift(); const orphan = targetGroup.children.shift();
if (orphan instanceof QueryClauseViewModel) { if (orphan instanceof QueryClauseViewModel) {
(<QueryClauseViewModel>orphan).clauseGroup = parent; (<QueryClauseViewModel>orphan).clauseGroup = parent;
@@ -71,14 +72,14 @@ export default class ClauseGroup {
} }
public removeAll(): void { public removeAll(): void {
var allClauses: QueryClauseViewModel[] = new Array<QueryClauseViewModel>(); const allClauses: QueryClauseViewModel[] = new Array<QueryClauseViewModel>();
this.flattenClausesImpl(this, allClauses); this.flattenClausesImpl(this, allClauses);
while (allClauses.length > 0) { while (allClauses.length > 0) {
allClauses.shift().dispose(); allClauses.shift().dispose();
} }
//eslint-disable-next-line
this.children = new Array<any>(); this.children = new Array<any>();
} }
@@ -87,12 +88,12 @@ export default class ClauseGroup {
*/ */
public groupSelectedItems(): boolean { public groupSelectedItems(): boolean {
// Find the selection start & end, also check for gaps between selected items (if found, cannot proceed). // Find the selection start & end, also check for gaps between selected items (if found, cannot proceed).
var selection = this.getCheckedItemsInfo(); const selection = this.getCheckedItemsInfo();
if (selection.canGroup) { if (selection.canGroup) {
var newGroup = new ClauseGroup(false, this); const newGroup = new ClauseGroup(false, this);
// Replace the selected items with the new group, and then move the selected items into the new group. // Replace the selected items with the new group, and then move the selected items into the new group.
var groupedItems = this.children.splice(selection.begin, selection.end - selection.begin + 1, newGroup); const groupedItems = this.children.splice(selection.begin, selection.end - selection.begin + 1, newGroup);
groupedItems && groupedItems &&
groupedItems.forEach((element) => { groupedItems.forEach((element) => {
@@ -118,13 +119,13 @@ export default class ClauseGroup {
return; return;
} }
var parentGroup = this.parentGroup; const parentGroup = this.parentGroup;
var index = parentGroup.children.indexOf(this); let index = parentGroup.children.indexOf(this);
if (index >= 0) { if (index >= 0) {
parentGroup.children.splice(index, 1); parentGroup.children.splice(index, 1);
var toPromote = this.children.splice(0, this.children.length); const toPromote = this.children.splice(0, this.children.length);
// Move all children one level up. // Move all children one level up.
toPromote && toPromote &&
@@ -146,16 +147,16 @@ export default class ClauseGroup {
} }
public findDeepestGroupInChildren(skipIndex?: number): ClauseGroup { public findDeepestGroupInChildren(skipIndex?: number): ClauseGroup {
var deepest: ClauseGroup = this; let deepest = <ClauseGroup>this;
var level: number = 0; let level = 0;
var func = (currentGroup: ClauseGroup): void => { const func = (currentGroup: ClauseGroup): void => {
level++; level++;
if (currentGroup.getCurrentGroupDepth() > deepest.getCurrentGroupDepth()) { if (currentGroup.getCurrentGroupDepth() > deepest.getCurrentGroupDepth()) {
deepest = currentGroup; deepest = currentGroup;
} }
for (var i = 0; i < currentGroup.children.length; i++) { for (let i = 0; i < currentGroup.children.length; i++) {
var currentItem = currentGroup.children[i]; const currentItem = currentGroup.children[i];
if ((i !== skipIndex || level > 1) && currentItem instanceof ClauseGroup) { if ((i !== skipIndex || level > 1) && currentItem instanceof ClauseGroup) {
func(currentItem); func(currentItem);
@@ -170,16 +171,16 @@ export default class ClauseGroup {
} }
private getCheckedItemsInfo(): { canGroup: boolean; begin: number; end: number } { private getCheckedItemsInfo(): { canGroup: boolean; begin: number; end: number } {
var beginIndex = -1; let beginIndex = -1;
var endIndex = -1; let endIndex = -1;
// In order to perform group, all selected items must be next to each other. // In order to perform group, all selected items must be next to each other.
// If one or more items are not selected between the first and the last selected item, the gapFlag will be set to True, meaning cannot perform group. // If one or more items are not selected between the first and the last selected item, the gapFlag will be set to True, meaning cannot perform group.
var gapFlag = false; let gapFlag = false;
var count = 0; let count = 0;
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
var subGroupSelectionState: { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean }; let subGroupSelectionState: { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean };
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
subGroupSelectionState = (<ClauseGroup>currentItem).getSelectionState(); subGroupSelectionState = (<ClauseGroup>currentItem).getSelectionState();
@@ -235,10 +236,10 @@ export default class ClauseGroup {
} }
private getSelectionState(): { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean } { private getSelectionState(): { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean } {
var selectedCount = 0; let selectedCount = 0;
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
if (currentItem instanceof ClauseGroup && (<ClauseGroup>currentItem).getSelectionState().allSelected) { if (currentItem instanceof ClauseGroup && (<ClauseGroup>currentItem).getSelectionState().allSelected) {
selectedCount++; selectedCount++;
@@ -260,8 +261,8 @@ export default class ClauseGroup {
} }
private unselectAll(): void { private unselectAll(): void {
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
(<ClauseGroup>currentItem).unselectAll(); (<ClauseGroup>currentItem).unselectAll();
@@ -278,8 +279,8 @@ export default class ClauseGroup {
targetArray.splice(0, targetArray.length); targetArray.splice(0, targetArray.length);
} }
for (var i = 0; i < queryGroup.children.length; i++) { for (let i = 0; i < queryGroup.children.length; i++) {
var currentItem = queryGroup.children[i]; const currentItem = queryGroup.children[i];
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
this.flattenClausesImpl(currentItem, targetArray); this.flattenClausesImpl(currentItem, targetArray);
@@ -292,13 +293,13 @@ export default class ClauseGroup {
} }
public getTreeDepth(): number { public getTreeDepth(): number {
var currentDepth = this.getCurrentGroupDepth(); let currentDepth = this.getCurrentGroupDepth();
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
var newDepth = (<ClauseGroup>currentItem).getTreeDepth(); const newDepth = (<ClauseGroup>currentItem).getTreeDepth();
if (newDepth > currentDepth) { if (newDepth > currentDepth) {
currentDepth = newDepth; currentDepth = newDepth;
@@ -310,8 +311,8 @@ export default class ClauseGroup {
} }
public getCurrentGroupDepth(): number { public getCurrentGroupDepth(): number {
var group = <ClauseGroup>this; let group = <ClauseGroup>this;
var depth = 0; let depth = 0;
while (!group.isRootGroup) { while (!group.isRootGroup) {
depth++; depth++;

View File

@@ -1,7 +1,7 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as Constants from "../Constants";
import ClauseGroup from "./ClauseGroup"; import ClauseGroup from "./ClauseGroup";
import QueryBuilderViewModel from "./QueryBuilderViewModel"; import QueryBuilderViewModel from "./QueryBuilderViewModel";
import * as Constants from "../Constants";
/** /**
* View model for showing group indicators on UI, contains information such as group color and border styles. * View model for showing group indicators on UI, contains information such as group color and border styles.
@@ -38,7 +38,7 @@ export default class ClauseGroupViewModel {
}; };
private getGroupBackgroundColor(group: ClauseGroup): string { private getGroupBackgroundColor(group: ClauseGroup): string {
var colorCount = Constants.clauseGroupColors.length; const colorCount = Constants.clauseGroupColors.length;
if (group.isRootGroup) { if (group.isRootGroup) {
return Constants.transparentColor; return Constants.transparentColor;

View File

@@ -29,7 +29,7 @@ export default class QueryBuilderViewModel {
public removeThisFilterLine = "Remove this filter line"; // localize public removeThisFilterLine = "Remove this filter line"; // localize
public groupSelectedClauses = "Group selected clauses"; // localize public groupSelectedClauses = "Group selected clauses"; // localize
public clauseArray = ko.observableArray<QueryClauseViewModel>(); // This is for storing the clauses in flattened form queryClauses for easier UI data binding. public clauseArray = ko.observableArray<QueryClauseViewModel>(); // This is for storing the clauses in flattened form queryClauses for easier UI data binding.
public queryClauses = new ClauseGroup(true, null); // The actual data structure containing the clause information. public queryClauses = new ClauseGroup(true, undefined); // The actual data structure containing the clause information.
public columnOptions: ko.ObservableArray<string>; public columnOptions: ko.ObservableArray<string>;
public canGroupClauses = ko.observable<boolean>(false); public canGroupClauses = ko.observable<boolean>(false);
@@ -107,7 +107,7 @@ export default class QueryBuilderViewModel {
} }
public setExample() { public setExample() {
var example1 = new QueryClauseViewModel( const example1 = new QueryClauseViewModel(
this, this,
"", "",
"PartitionKey", "PartitionKey",
@@ -121,7 +121,7 @@ export default class QueryBuilderViewModel {
//null, //null,
true true
); );
var example2 = new QueryClauseViewModel( const example2 = new QueryClauseViewModel(
this, this,
"And", "And",
"RowKey", "RowKey",
@@ -140,13 +140,13 @@ export default class QueryBuilderViewModel {
} }
public getODataFilterFromClauses = (): string => { public getODataFilterFromClauses = (): string => {
var filterString: string = ""; let filterString = "";
var treeTraversal = (group: ClauseGroup): void => { const treeTraversal = (group: ClauseGroup): void => {
for (var i = 0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
var currentItem = group.children[i]; const currentItem = group.children[i];
if (currentItem instanceof QueryClauseViewModel) { if (currentItem instanceof QueryClauseViewModel) {
var clause = <QueryClauseViewModel>currentItem; const clause = <QueryClauseViewModel>currentItem;
this.timestampToValue(clause); this.timestampToValue(clause);
filterString = filterString.concat( filterString = filterString.concat(
this.constructODataClause( this.constructODataClause(
@@ -173,7 +173,7 @@ export default class QueryBuilderViewModel {
}; };
public getSqlFilterFromClauses = (): string => { public getSqlFilterFromClauses = (): string => {
var filterString: string = "SELECT * FROM c"; let filterString = "SELECT * FROM c";
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) { if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
filterString = "SELECT"; filterString = "SELECT";
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText(); const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
@@ -199,15 +199,15 @@ export default class QueryBuilderViewModel {
return filterString; return filterString;
} }
filterString = filterString.concat(" WHERE"); filterString = filterString.concat(" WHERE");
var first = true; let first = true;
var treeTraversal = (group: ClauseGroup): void => { const treeTraversal = (group: ClauseGroup): void => {
for (var i = 0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
var currentItem = group.children[i]; const currentItem = group.children[i];
if (currentItem instanceof QueryClauseViewModel) { if (currentItem instanceof QueryClauseViewModel) {
var clause = <QueryClauseViewModel>currentItem; const clause = <QueryClauseViewModel>currentItem;
let timeStampValue: string = this.timestampToSqlValue(clause); const timeStampValue: string = this.timestampToSqlValue(clause);
var value = clause.value(); let value = clause.value();
if (!clause.isValue()) { if (!clause.isValue()) {
value = timeStampValue; value = timeStampValue;
} }
@@ -240,7 +240,7 @@ export default class QueryBuilderViewModel {
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId; const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
const collectionId = this._queryViewModel.queryTablesTab.collection.id(); const collectionId = this._queryViewModel.queryTablesTab.collection.id();
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`; const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
var filterString: string = `SELECT * FROM ${tableToQuery}`; let filterString = `SELECT * FROM ${tableToQuery}`;
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) { if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
filterString = "SELECT"; filterString = "SELECT";
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText(); const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
@@ -255,15 +255,15 @@ export default class QueryBuilderViewModel {
return filterString; return filterString;
} }
filterString = filterString.concat(" WHERE"); filterString = filterString.concat(" WHERE");
var first = true; let first = true;
var treeTraversal = (group: ClauseGroup): void => { const treeTraversal = (group: ClauseGroup): void => {
for (var i = 0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
var currentItem = group.children[i]; const currentItem = group.children[i];
if (currentItem instanceof QueryClauseViewModel) { if (currentItem instanceof QueryClauseViewModel) {
var clause = <QueryClauseViewModel>currentItem; const clause = <QueryClauseViewModel>currentItem;
let timeStampValue: string = this.timestampToSqlValue(clause); const timeStampValue = this.timestampToSqlValue(clause);
var value = clause.value(); let value = clause.value();
if (!clause.isValue()) { if (!clause.isValue()) {
value = timeStampValue; value = timeStampValue;
} }
@@ -293,13 +293,13 @@ export default class QueryBuilderViewModel {
}; };
public updateColumnOptions = (): void => { public updateColumnOptions = (): void => {
let originalHeaders = this.columnOptions(); // let originalHeaders = this.columnOptions();
let newHeaders = this.tableEntityListViewModel.headers; const newHeaders = this.tableEntityListViewModel.headers;
this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns)); this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns));
}; };
private generateLeftParentheses(clause: QueryClauseViewModel): string { private generateLeftParentheses(clause: QueryClauseViewModel): string {
var result = ""; let result = "";
if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) { if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) {
return result; return result;
@@ -307,7 +307,7 @@ export default class QueryBuilderViewModel {
result = result.concat("("); result = result.concat("(");
} }
var currentGroup: ClauseGroup = clause.clauseGroup; let currentGroup: ClauseGroup = clause.clauseGroup;
while ( while (
!currentGroup.isRootGroup && !currentGroup.isRootGroup &&
@@ -322,7 +322,7 @@ export default class QueryBuilderViewModel {
} }
private generateRightParentheses(clause: QueryClauseViewModel): string { private generateRightParentheses(clause: QueryClauseViewModel): string {
var result = ""; let result = "";
if ( if (
clause.clauseGroup.isRootGroup || clause.clauseGroup.isRootGroup ||
@@ -333,7 +333,7 @@ export default class QueryBuilderViewModel {
result = result.concat(")"); result = result.concat(")");
} }
var currentGroup: ClauseGroup = clause.clauseGroup; let currentGroup: ClauseGroup = clause.clauseGroup;
while ( while (
!currentGroup.isRootGroup && !currentGroup.isRootGroup &&
@@ -364,14 +364,17 @@ export default class QueryBuilderViewModel {
case Constants.TableType.String: case Constants.TableType.String:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
operator operator
// eslint-disable-next-line no-useless-escape
)} \'${value}\'${rightParentheses}`; )} \'${value}\'${rightParentheses}`;
case Constants.TableType.Guid: case Constants.TableType.Guid:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
operator operator
// eslint-disable-next-line no-useless-escape
)} guid\'${value}\'${rightParentheses}`; )} guid\'${value}\'${rightParentheses}`;
case Constants.TableType.Binary: case Constants.TableType.Binary:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
operator operator
// eslint-disable-next-line no-useless-escape
)} binary\'${value}\'${rightParentheses}`; )} binary\'${value}\'${rightParentheses}`;
default: default:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
@@ -391,9 +394,11 @@ export default class QueryBuilderViewModel {
): string => { ): string => {
if (propertyName === Constants.EntityKeyNames.PartitionKey) { if (propertyName === Constants.EntityKeyNames.PartitionKey) {
propertyName = TableEntityProcessor.keyProperties.PartitionKey; propertyName = TableEntityProcessor.keyProperties.PartitionKey;
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c["${propertyName}"] ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c["${propertyName}"] ${operator} \'${value}\'${rightParentheses}`;
} else if (propertyName === Constants.EntityKeyNames.RowKey) { } else if (propertyName === Constants.EntityKeyNames.RowKey) {
propertyName = TableEntityProcessor.keyProperties.Id; propertyName = TableEntityProcessor.keyProperties.Id;
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} \'${value}\'${rightParentheses}`;
} else if (propertyName === Constants.EntityKeyNames.Timestamp) { } else if (propertyName === Constants.EntityKeyNames.Timestamp) {
propertyName = TableEntityProcessor.keyProperties.Timestamp; propertyName = TableEntityProcessor.keyProperties.Timestamp;
@@ -403,16 +408,21 @@ export default class QueryBuilderViewModel {
} }
switch (type) { switch (type) {
case Constants.TableType.DateTime: case Constants.TableType.DateTime:
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${DateTimeUtilities.convertJSDateToTicksWithPadding( return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${DateTimeUtilities.convertJSDateToTicksWithPadding(
value value
// eslint-disable-next-line no-useless-escape
)}\'${rightParentheses}`; )}\'${rightParentheses}`;
case Constants.TableType.Int64: case Constants.TableType.Int64:
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${Utilities.padLongWithZeros( return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${Utilities.padLongWithZeros(
value value
// eslint-disable-next-line no-useless-escape
)}\'${rightParentheses}`; )}\'${rightParentheses}`;
case Constants.TableType.String: case Constants.TableType.String:
case Constants.TableType.Guid: case Constants.TableType.Guid:
case Constants.TableType.Binary: case Constants.TableType.Binary:
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${value}\'${rightParentheses}`;
default: default:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} ${value}${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} ${value}${rightParentheses}`;
@@ -434,6 +444,7 @@ export default class QueryBuilderViewModel {
type === Constants.CassandraType.Ascii || type === Constants.CassandraType.Ascii ||
type === Constants.CassandraType.Varchar type === Constants.CassandraType.Varchar
) { ) {
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} \'${value}\'${rightParentheses}`;
} }
return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} ${value}${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} ${value}${rightParentheses}`;
@@ -454,7 +465,7 @@ export default class QueryBuilderViewModel {
case Constants.Operator.NotEqualTo: case Constants.Operator.NotEqualTo:
return Constants.ODataOperator.NotEqualTo; return Constants.ODataOperator.NotEqualTo;
} }
return null; return undefined;
}; };
public groupClauses = (): void => { public groupClauses = (): void => {
@@ -463,11 +474,11 @@ export default class QueryBuilderViewModel {
this.updateCanGroupClauses(); this.updateCanGroupClauses();
}; };
public addClauseIndex = (index: number, data: any): void => { public addClauseIndex = (index: number): void => {
if (index < 0) { if (index < 0) {
index = 0; index = 0;
} }
var newClause = new QueryClauseViewModel( const newClause = new QueryClauseViewModel(
this, this,
"And", "And",
"", "",
@@ -492,28 +503,28 @@ export default class QueryBuilderViewModel {
// adds a new clause to the end of the array // adds a new clause to the end of the array
public addNewClause = (): void => { public addNewClause = (): void => {
this.addClauseIndex(this.clauseArray().length, null); this.addClauseIndex(this.clauseArray().length);
}; };
public onAddClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => { public onAddClauseKeyDown = (index: number, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.addClauseIndex(index, data); this.addClauseIndex(index);
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
return true; return true;
}; };
public onAddNewClauseKeyDown = (source: any, event: KeyboardEvent): boolean => { public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.addClauseIndex(this.clauseArray().length - 1, null); this.addClauseIndex(this.clauseArray().length - 1);
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
return true; return true;
}; };
public deleteClause = (index: number, data: any): void => { public deleteClause = (index: number): void => {
this.deleteClauseImpl(index); this.deleteClauseImpl(index);
if (this.clauseArray().length !== 0) { if (this.clauseArray().length !== 0) {
this.clauseArray()[0].and_or(""); this.clauseArray()[0].and_or("");
@@ -523,9 +534,9 @@ export default class QueryBuilderViewModel {
$(window).resize(); $(window).resize();
}; };
public onDeleteClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => { public onDeleteClauseKeyDown = (index: number, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.deleteClause(index, data); this.deleteClause(index);
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
@@ -539,25 +550,26 @@ export default class QueryBuilderViewModel {
* (transparent) or its parent group view models. * (transparent) or its parent group view models.
*/ */
public getClauseGroupViewModels = (clause: QueryClauseViewModel): ClauseGroupViewModel[] => { public getClauseGroupViewModels = (clause: QueryClauseViewModel): ClauseGroupViewModel[] => {
var placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this); const placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this);
var treeDepth = this.queryClauses.getTreeDepth(); const treeDepth = this.queryClauses.getTreeDepth();
var groupViewModels = new Array<ClauseGroupViewModel>(treeDepth); const groupViewModels = new Array<ClauseGroupViewModel>(treeDepth);
// Prefill the arry with placeholders. // Prefill the arry with placeholders.
for (var i = 0; i < groupViewModels.length; i++) { for (let i = 0; i < groupViewModels.length; i++) {
groupViewModels[i] = placeHolderGroupViewModel; groupViewModels[i] = placeHolderGroupViewModel;
} }
var currentGroup = clause.clauseGroup; let currentGroup = clause.clauseGroup;
// This function determines whether the path from clause to the current group is on the left most. // This function determines whether the path from clause to the current group is on the left most.
var isLeftMostPath = (): boolean => { const isLeftMostPath = (): boolean => {
var group = clause.clauseGroup; let group = clause.clauseGroup;
if (group.children.indexOf(clause) !== 0) { if (group.children.indexOf(clause) !== 0) {
return false; return false;
} }
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
if (group.getId() === currentGroup.getId()) { if (group.getId() === currentGroup.getId()) {
break; break;
@@ -573,13 +585,14 @@ export default class QueryBuilderViewModel {
}; };
// This function determines whether the path from clause to the current group is on the right most. // This function determines whether the path from clause to the current group is on the right most.
var isRightMostPath = (): boolean => { const isRightMostPath = (): boolean => {
var group = clause.clauseGroup; let group = clause.clauseGroup;
if (group.children.indexOf(clause) !== group.children.length - 1) { if (group.children.indexOf(clause) !== group.children.length - 1) {
return false; return false;
} }
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
if (group.getId() === currentGroup.getId()) { if (group.getId() === currentGroup.getId()) {
break; break;
@@ -594,26 +607,26 @@ export default class QueryBuilderViewModel {
return true; return true;
}; };
var vmIndex = groupViewModels.length - 1; let vmIndex = groupViewModels.length - 1;
var skipIndex = -1; let skipIndex = -1;
var lastDepth = clause.groupDepth; let lastDepth = clause.groupDepth;
while (!currentGroup.isRootGroup) { while (!currentGroup.isRootGroup) {
// The current group will be rendered at least once, and if there are any sibling groups deeper // The current group will be rendered at least once, and if there are any sibling groups deeper
// than the current group, we will repeat rendering the current group to fill up the gap between // than the current group, we will repeat rendering the current group to fill up the gap between
// current & deepest sibling. // current & deepest sibling.
var deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth(); const deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth();
// Find out the depth difference between the deepest group under the siblings of currentGroup and // Find out the depth difference between the deepest group under the siblings of currentGroup and
// the deepest group under currentGroup. If the result n is a positive number, it means there are // the deepest group under currentGroup. If the result n is a positive number, it means there are
// deeper groups in siblings and we need to draw n + 1 group blocks on UI to fill up the depth // deeper groups in siblings and we need to draw n + 1 group blocks on UI to fill up the depth
// differences. If the result n is a negative number, it means current group contains the deepest // differences. If the result n is a negative number, it means current group contains the deepest
// sub-group, we only need to draw the group block once. // sub-group, we only need to draw the group block once.
var repeatCount = Math.max(deepestInSiblings - lastDepth, 0); const repeatCount = Math.max(deepestInSiblings - lastDepth, 0);
for (var i = 0; i <= repeatCount; i++) { for (let i = 0; i <= repeatCount; i++) {
var isLeftMost = isLeftMostPath(); const isLeftMost = isLeftMostPath();
var isRightMost = isRightMostPath(); const isRightMost = isRightMostPath();
var groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this); const groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this);
groupViewModel.showTopBorder(isLeftMost); groupViewModel.showTopBorder(isLeftMost);
groupViewModel.showBottomBorder(isRightMost); groupViewModel.showBottomBorder(isRightMost);
@@ -635,9 +648,9 @@ export default class QueryBuilderViewModel {
}; };
public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void { public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void {
var index = this.clauseArray.peek().indexOf(clauseToAdd); const index = this.clauseArray.peek().indexOf(clauseToAdd);
var newClause = new QueryClauseViewModel( const newClause = new QueryClauseViewModel(
this, this,
//this._tableEntityListViewModel.tableExplorerContext.hostProxy, //this._tableEntityListViewModel.tableExplorerContext.hostProxy,
"And", "And",
@@ -662,10 +675,10 @@ export default class QueryBuilderViewModel {
} }
private scrollToBottom(): void { private scrollToBottom(): void {
var scrollBox = document.getElementById("scroll"); const scrollBox = document.getElementById("scroll");
if (!this.scrollEventListener) { if (!this.scrollEventListener) {
scrollBox.addEventListener("scroll", function () { scrollBox.addEventListener("scroll", function () {
var translate = "translate(0," + this.scrollTop + "px)"; const translate = "translate(0," + this.scrollTop + "px)";
const allTh = <NodeListOf<HTMLElement>>this.querySelectorAll("thead td"); const allTh = <NodeListOf<HTMLElement>>this.querySelectorAll("thead td");
for (let i = 0; i < allTh.length; i++) { for (let i = 0; i < allTh.length; i++) {
allTh[i].style.transform = translate; allTh[i].style.transform = translate;
@@ -673,7 +686,7 @@ export default class QueryBuilderViewModel {
}); });
this.scrollEventListener = true; this.scrollEventListener = true;
} }
var isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1; const isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1;
if (isScrolledToBottom) { if (isScrolledToBottom) {
scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.clientHeight; scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.clientHeight;
} }
@@ -685,8 +698,8 @@ export default class QueryBuilderViewModel {
} }
private deleteClauseImpl(index: number): void { private deleteClauseImpl(index: number): void {
var clause = this.clauseArray()[index]; const clause = this.clauseArray()[index];
var previousClause = index === 0 ? 0 : index - 1; const previousClause = index === 0 ? 0 : index - 1;
this.queryClauses.deleteClause(clause); this.queryClauses.deleteClause(clause);
this.updateClauseArray(); this.updateClauseArray();
if (this.clauseArray()[previousClause]) { if (this.clauseArray()[previousClause]) {
@@ -731,7 +744,7 @@ export default class QueryBuilderViewModel {
private timestampToSqlValue(clause: QueryClauseViewModel): string { private timestampToSqlValue(clause: QueryClauseViewModel): string {
if (clause.isValue()) { if (clause.isValue()) {
return null; return undefined;
} else if (clause.isTimestamp()) { } else if (clause.isTimestamp()) {
return this.getTimeStampToSqlQuery(clause); return this.getTimeStampToSqlQuery(clause);
// } else if (clause.isCustomLastTimestamp()) { // } else if (clause.isCustomLastTimestamp()) {
@@ -743,7 +756,7 @@ export default class QueryBuilderViewModel {
return clause.customTimeValue(); return clause.customTimeValue();
} }
} }
return null; return undefined;
} }
private getTimeStampToQuery(clause: QueryClauseViewModel): void { private getTimeStampToQuery(clause: QueryClauseViewModel): void {
@@ -789,7 +802,7 @@ export default class QueryBuilderViewModel {
case Constants.timeOptions.currentYear: case Constants.timeOptions.currentYear:
return CustomTimestampHelper._queryCurrentYearLocal(); return CustomTimestampHelper._queryCurrentYearLocal();
} }
return null; return undefined;
} }
public checkIfClauseChanged(): void { public checkIfClauseChanged(): void {

View File

@@ -14,7 +14,7 @@ export default class QueryClauseViewModel {
public field: ko.Observable<string>; public field: ko.Observable<string>;
public type: ko.Observable<string>; public type: ko.Observable<string>;
public operator: ko.Observable<string>; public operator: ko.Observable<string>;
public value: ko.Observable<any>; public value: ko.Observable<string>;
public timeValue: ko.Observable<string>; public timeValue: ko.Observable<string>;
public customTimeValue: ko.Observable<string>; public customTimeValue: ko.Observable<string>;
public canAnd: ko.Observable<boolean>; public canAnd: ko.Observable<boolean>;
@@ -39,7 +39,7 @@ export default class QueryClauseViewModel {
field: string, field: string,
type: string, type: string,
operator: string, operator: string,
value: any, value: string,
canAnd: boolean, canAnd: boolean,
timeValue: string, timeValue: string,
customTimeValue: string, customTimeValue: string,
@@ -88,30 +88,30 @@ export default class QueryClauseViewModel {
userContext.apiType !== "Cassandra" userContext.apiType !== "Cassandra"
); );
this.and_or.subscribe((value) => { this.and_or.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this.field.subscribe((value) => { this.field.subscribe(() => {
this.changeField(); this.changeField();
}); });
this.type.subscribe((value) => { this.type.subscribe(() => {
this.changeType(); this.changeType();
}); });
this.timeValue.subscribe((value) => { this.timeValue.subscribe(() => {
// if (this.timeValue() === QueryBuilderConstants.timeOptions.custom) { // if (this.timeValue() === QueryBuilderConstants.timeOptions.custom) {
// this.customTimestampDialog(); // this.customTimestampDialog();
// } // }
}); });
this.customTimeValue.subscribe((value) => { this.customTimeValue.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this.value.subscribe((value) => { this.value.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this.operator.subscribe((value) => { this.operator.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this._groupCheckSubscription = this.checkedForGrouping.subscribe((value) => { this._groupCheckSubscription = this.checkedForGrouping.subscribe(() => {
this._queryBuilderViewModel.updateCanGroupClauses(); this._queryBuilderViewModel.updateCanGroupClauses();
}); });
this.isAndOrFocused = ko.observable<boolean>(false); this.isAndOrFocused = ko.observable<boolean>(false);
@@ -280,7 +280,7 @@ export default class QueryClauseViewModel {
this._groupCheckSubscription.dispose(); this._groupCheckSubscription.dispose();
} }
this.clauseGroup = null; this.clauseGroup = undefined;
this._queryBuilderViewModel = null; this._queryBuilderViewModel = undefined;
} }
} }

View File

@@ -999,7 +999,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
"data-order": 2, "data-order": 2,
"data-title": "Query Stats", "data-title": "Query Stats",
}} }}
style={{ height: "100%" }} style={{ height: "100%", overflowY: "scroll" }}
> >
{this.state.allResultsMetadata.length > 0 && !this.state.error && ( {this.state.allResultsMetadata.length > 0 && !this.state.error && (
<div className="queryMetricsSummaryContainer"> <div className="queryMetricsSummaryContainer">

Some files were not shown because too many files have changed in this diff Show More