mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-24 11:21:23 +00:00
Compare commits
51 Commits
resolve_Co
...
migrate_qu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cec6b27d2e | ||
|
|
7d905159c6 | ||
|
|
126a572078 | ||
|
|
597357e62d | ||
|
|
6c545c454d | ||
|
|
d4817de14e | ||
|
|
43b6e25e86 | ||
|
|
30fa40c50e | ||
|
|
1f2e528f46 | ||
|
|
e431692e0d | ||
|
|
1985843b93 | ||
|
|
5287e6e29f | ||
|
|
27e99d7f2f | ||
|
|
884f06a729 | ||
|
|
b7cb0b55a6 | ||
|
|
c1c085f712 | ||
|
|
2b22e518e0 | ||
|
|
e95245f1df | ||
|
|
046e6eb5a4 | ||
|
|
d7963b3ef4 | ||
|
|
d70dd6bc7e | ||
|
|
87a368858c | ||
|
|
e1d32bde36 | ||
|
|
91d86dd271 | ||
|
|
0df0c4b420 | ||
|
|
07b3e30f05 | ||
|
|
1931e775d9 | ||
|
|
fe684fd6d2 | ||
|
|
c681c14be1 | ||
|
|
0cb897451d | ||
|
|
18217b71c6 | ||
|
|
d805a0ba4a | ||
|
|
ec3ac87a20 | ||
|
|
9a74a8c2ab | ||
|
|
8a1920714d | ||
|
|
7fa5883072 | ||
|
|
d062c85e94 | ||
|
|
239c5f6895 | ||
|
|
594c4026d5 | ||
|
|
dc08ba740e | ||
|
|
f1ffa968a7 | ||
|
|
8ec551f6e6 | ||
|
|
08d04295b1 | ||
|
|
13f94e83f0 | ||
|
|
4f632b234f | ||
|
|
754f0b392c | ||
|
|
3bce7f764e | ||
|
|
079593cec4 | ||
|
|
27cc1337ef | ||
|
|
bb5aecac1b | ||
|
|
c4a1d4cea7 |
@@ -6,7 +6,7 @@ src/Api/Apis.ts
|
||||
src/AuthType.ts
|
||||
src/Bindings/BindingHandlersRegisterer.ts
|
||||
src/Bindings/ReactBindingHandler.ts
|
||||
src/Common/Constants/index.ts
|
||||
src/Common/Constants.ts
|
||||
src/Common/CosmosClient.test.ts
|
||||
src/Common/CosmosClient.ts
|
||||
src/Common/DataAccessUtilityBase.test.ts
|
||||
@@ -80,14 +80,18 @@ src/Explorer/Tables/DataTable/CacheBase.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBuilder.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/DataTableViewModel.ts
|
||||
# src/Explorer/Tables/DataTable/TableCommands.ts
|
||||
# src/Explorer/Tables/DataTable/TableEntityCache.ts
|
||||
src/Explorer/Tables/DataTable/TableCommands.ts
|
||||
src/Explorer/Tables/DataTable/TableEntityCache.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/QueryBuilderViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
||||
src/Explorer/Tables/TableDataClient.ts
|
||||
src/Explorer/Tables/TableEntityProcessor.ts
|
||||
src/Explorer/Tables/Utilities.ts
|
||||
@@ -111,10 +115,15 @@ src/Explorer/Tree/ObjectId.ts
|
||||
src/Explorer/Tree/ResourceTokenCollection.ts
|
||||
src/Explorer/Tree/StoredProcedure.ts
|
||||
src/Explorer/Tree/TreeComponents.ts
|
||||
src/Explorer/Tree/Trigger.ts
|
||||
src/Explorer/WaitsForTemplateViewModel.ts
|
||||
src/GitHub/GitHubClient.test.ts
|
||||
src/GitHub/GitHubClient.ts
|
||||
src/GitHub/GitHubConnector.ts
|
||||
src/GitHub/GitHubOAuthService.ts
|
||||
src/Index.ts
|
||||
src/Juno/JunoClient.test.ts
|
||||
src/Juno/JunoClient.ts
|
||||
src/Platform/Hosted/Authorization.ts
|
||||
src/ReactDevTools.ts
|
||||
src/Shared/Constants.ts
|
||||
@@ -134,7 +143,7 @@ src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||
src/Explorer/Controls/TreeComponent/TreeComponent.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/LeftPaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||
@@ -144,10 +153,10 @@ src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.t
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||
src/Explorer/Menus/CommandBar/CommandBarUtil.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/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/decorators/draggable/index.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
|
||||
|
||||
@@ -39,6 +39,7 @@ module.exports = {
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"no-null/no-null": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||
eqeqeq: "error",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -22,6 +22,5 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ module.exports = {
|
||||
global: {
|
||||
branches: 25,
|
||||
functions: 25,
|
||||
lines: 29,
|
||||
statements: 29,
|
||||
lines: 29.5,
|
||||
statements: 29.5,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -129,8 +129,6 @@ module.exports = {
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-jsdom",
|
||||
|
||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
|
||||
@@ -3,337 +3,358 @@
|
||||
@import "../Common/Constants";
|
||||
|
||||
.query-panel {
|
||||
display: table;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border-top: 1px solid #DDDDDD;
|
||||
/*[{environment-commandbar-toolbar-separator}]*/
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
padding: 2px 0px 0px 2px;
|
||||
resize: vertical;
|
||||
display: table;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border-top: 1px solid #dddddd;
|
||||
/*[{environment-commandbar-toolbar-separator}]*/
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
padding: 2px 0px 0px 2px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.query-panel .row {
|
||||
display: table-row;
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.query-panel .row .cell {
|
||||
display: table-cell;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.query-panel.transition-in {
|
||||
display: table;
|
||||
top: 0px;
|
||||
-webkit-transition: top 2s linear;
|
||||
-ms-transition: top 2s linear;
|
||||
-moz-transition: top 2s linear;
|
||||
-khtml-transition: top 2s linear;
|
||||
-o-transition: top 2s linear;
|
||||
transition: top 2s linear;
|
||||
display: table;
|
||||
top: 0px;
|
||||
-webkit-transition: top 2s linear;
|
||||
-ms-transition: top 2s linear;
|
||||
-moz-transition: top 2s linear;
|
||||
-khtml-transition: top 2s linear;
|
||||
-o-transition: top 2s linear;
|
||||
transition: top 2s linear;
|
||||
}
|
||||
|
||||
.query-builder {
|
||||
width:100%;
|
||||
padding-right: @DefaultSpace;
|
||||
border-bottom: 1px solid @BaseMedium;
|
||||
margin-bottom: @DefaultSpace;
|
||||
width: 100%;
|
||||
padding-right: @DefaultSpace;
|
||||
border-bottom: 1px solid @BaseMedium;
|
||||
margin-bottom: @DefaultSpace;
|
||||
}
|
||||
|
||||
.query-builder-toolbar {
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
min-width: 600px;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid #DDDDDD;
|
||||
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
min-width: 600px;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
margin: 2px 0px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
margin: 2px 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button {
|
||||
min-width: 0px;
|
||||
padding: 0px;
|
||||
margin-left: 2px;
|
||||
background-color: transparent;
|
||||
border: solid transparent;
|
||||
min-width: 0px;
|
||||
padding: 0px;
|
||||
margin-left: 2px;
|
||||
background-color: transparent;
|
||||
border: solid transparent;
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
|
||||
outline: 2px solid dodgerblue;
|
||||
/*[2px solid {common-common-controls-button-border-hover}]*/
|
||||
outline: 2px solid dodgerblue;
|
||||
/*[2px solid {common-common-controls-button-border-hover}]*/
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
|
||||
background-color: #CCCEDB;
|
||||
/*[{common-controls-button-hover-background}]*/
|
||||
background-color: #cccedb;
|
||||
/*[{common-controls-button-hover-background}]*/
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
|
||||
background-color: #E6E7ED;
|
||||
/*[{common-controls-inner-tab-active-background}]*/
|
||||
outline: none
|
||||
background-color: #e6e7ed;
|
||||
/*[{common-controls-inner-tab-active-background}]*/
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
|
||||
background-color: #ffffff;
|
||||
/*[{common-controls-button-disabled-background}]*/
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
background-color: #ffffff;
|
||||
/*[{common-controls-button-disabled-background}]*/
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.tableContainer {
|
||||
overflow: hidden;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
overflow: hidden;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tablesQueryTab{
|
||||
padding-left: @MediumSpace;
|
||||
width: 100%;
|
||||
margin-bottom:@LargeSpace;
|
||||
.tablesQueryTab {
|
||||
padding-left: @MediumSpace;
|
||||
width: 100%;
|
||||
margin-bottom: 60px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.entity-error-Img {
|
||||
width: @WarningErrorIconSize;
|
||||
height: @WarningErrorIconSize;
|
||||
margin: @DefaultSpace 0px 0px @SmallSpace;
|
||||
width: @WarningErrorIconSize;
|
||||
height: @WarningErrorIconSize;
|
||||
margin: @DefaultSpace 0px 0px @SmallSpace;
|
||||
}
|
||||
|
||||
.query-editor-panel {
|
||||
margin-right: 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 25px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
margin-right: 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 25px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.query-editor-text {
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: solid 1px #A9ACB3;
|
||||
/*[{plugin-textbox-disabled-color}]*/
|
||||
resize: none;
|
||||
margin-top: -39px;
|
||||
background-color: #ddd;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: solid 1px #a9acb3;
|
||||
/*[{plugin-textbox-disabled-color}]*/
|
||||
resize: none;
|
||||
margin-top: -39px;
|
||||
background-color: #ddd;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.error-bar {
|
||||
padding: @LargeSpace 34px @MediumSpace 24px;
|
||||
padding: @LargeSpace 34px @MediumSpace 24px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: @BaseLow;
|
||||
padding: @DefaultSpace;
|
||||
display: inline-flex;
|
||||
background-color: @BaseLow;
|
||||
padding: @DefaultSpace;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
padding-left: @MediumSpace;
|
||||
padding-left: @MediumSpace;
|
||||
}
|
||||
|
||||
.query-editor-text-invalid {
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: 1px solid #e51400;
|
||||
resize: none;
|
||||
margin-top: -30px;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: 1px solid #e51400;
|
||||
resize: none;
|
||||
margin-top: -30px;
|
||||
}
|
||||
|
||||
.query-editor-panel .warning-bar {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
}
|
||||
|
||||
.query-editor-panel .warning-bar .warning-message {
|
||||
display: inline-flex;
|
||||
padding-top: 2px;
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
padding-top: 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.query-editor-panel .warning-bar .warning-message .warning-text {
|
||||
margin-left: 2px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.advanced-options-panel{
|
||||
margin-bottom: @DefaultSpace;
|
||||
.advanced-options-panel {
|
||||
margin-bottom: @DefaultSpace;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-heading .advanced-title {
|
||||
display: inline-flex;
|
||||
margin-left: 27px;
|
||||
margin-top: 10px;
|
||||
cursor: default;
|
||||
display: inline-flex;
|
||||
margin-left: 27px;
|
||||
margin-top: 10px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options {
|
||||
margin-left: 32px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid transparent;
|
||||
margin-left: 32px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 12px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 12px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .top .top-input {
|
||||
width: 100px;
|
||||
word-spacing: normal;
|
||||
color: #1E1E1E;
|
||||
/*[{common-controls-button-foreground}]*/
|
||||
border: 1px solid #CCCEDB;
|
||||
/*[1px solid {plugin-textbox-border-color}]*/
|
||||
height: 20px;
|
||||
margin-left: 8px;
|
||||
width: 100px;
|
||||
word-spacing: normal;
|
||||
color: #1e1e1e;
|
||||
/*[{common-controls-button-foreground}]*/
|
||||
border: 1px solid #cccedb;
|
||||
/*[1px solid {plugin-textbox-border-color}]*/
|
||||
height: 20px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .top .invalid-top {
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .select {
|
||||
margin-top: 18px;
|
||||
display: inline-flex;
|
||||
margin-top: 18px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.advanced-options-icon {
|
||||
margin-left: 2px;
|
||||
vertical-align: sub;
|
||||
margin-left: 2px;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .select .select-options-text {
|
||||
margin-left: 4px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .select .select-options-link {
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.query-panel .row .column-headers .Field {
|
||||
padding-left: 95px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 95px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.clause-table {
|
||||
border-spacing: 0px;
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: -3px;
|
||||
border-spacing: 0px;
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.clause-table-row {
|
||||
display: row;
|
||||
margin-bottom: 10px;
|
||||
display: row;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clause-table-cell {
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.action-column>button,
|
||||
.group-control-header>button,
|
||||
.group-indicator-column>button {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
.action-column > button,
|
||||
.group-control-header > button,
|
||||
.group-indicator-column > button {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.group-control-header>button:disabled {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
.group-control-header > button:disabled {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.clause-table-field {
|
||||
width: 100%;
|
||||
border: 1px solid #bbbbbb;
|
||||
width: 100%;
|
||||
border: 1px solid #bbbbbb;
|
||||
}
|
||||
|
||||
.clause-table-cell button {
|
||||
height: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.clause-table-cell input[type="checkbox"] {
|
||||
padding: 0px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.and-or-svg {
|
||||
margin-top: -8px;
|
||||
margin-right: -5px;
|
||||
margin-top: -8px;
|
||||
margin-right: -26px;
|
||||
}
|
||||
|
||||
.and-or-label {
|
||||
margin-left: 52px;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
margin-left: 69px;
|
||||
}
|
||||
.data-type-label {
|
||||
margin-left: 54px;
|
||||
}
|
||||
|
||||
.operator-label {
|
||||
margin-left: 80px;
|
||||
}
|
||||
.value-label {
|
||||
margin-left: 62px;
|
||||
}
|
||||
|
||||
.scroll-box {
|
||||
border-bottom: 1px transparent #DDD;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
border-top: 1px transparent #DDD;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
max-height: 20vh;
|
||||
width: 100%;
|
||||
border-bottom: 1px transparent #ddd;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
border-top: 1px transparent #ddd;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
max-height: 20vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.and-or-column,
|
||||
.and-or-header {
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
padding-left: 5px;
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.operator-column,
|
||||
.operator-header {
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.field-header,
|
||||
.field-column {
|
||||
min-width: 125px;
|
||||
padding-right: 10px;
|
||||
min-width: 125px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.type-header,
|
||||
.type-column {
|
||||
min-width: 85px;
|
||||
min-width: 85px;
|
||||
}
|
||||
|
||||
.and-or-column,
|
||||
@@ -345,41 +366,41 @@ input::-webkit-inner-spin-button {
|
||||
.type-header,
|
||||
.type-column,
|
||||
.action-header {
|
||||
padding-right: 10px;
|
||||
margin-bottom: 8px;
|
||||
padding-right: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.value-header,
|
||||
.value-column,
|
||||
.time-column {
|
||||
min-width: 230px;
|
||||
padding: 0px 4px 0px 0px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
min-width: 230px;
|
||||
padding: 0px 4px 0px 0px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.group-control-header,
|
||||
.group-control-column {
|
||||
min-width: 25px;
|
||||
text-align: right;
|
||||
min-width: 25px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.group-indicator-table {
|
||||
border-spacing: 0px;
|
||||
min-height: 24px
|
||||
border-spacing: 0px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.group-indicator-column {
|
||||
min-width: 21px;
|
||||
padding: 0px;
|
||||
border-style: none;
|
||||
height: 29px;
|
||||
min-width: 21px;
|
||||
padding: 0px;
|
||||
border-style: none;
|
||||
height: 29px;
|
||||
}
|
||||
|
||||
.clause-table-cell.action-column,
|
||||
.clause-table-cell.action-column,
|
||||
.clause-table-cell.action-header {
|
||||
min-width: 60px;
|
||||
padding-left: @SmallSpace;
|
||||
min-width: 60px;
|
||||
padding-left: @SmallSpace;
|
||||
}
|
||||
|
||||
.action-header,
|
||||
@@ -388,15 +409,14 @@ input::-webkit-inner-spin-button {
|
||||
.operator-header,
|
||||
.value-header,
|
||||
.and-or-header {
|
||||
padding-right: 4px;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 4px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.header-background {
|
||||
background-color: #ffffff;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
/*.type-header {
|
||||
padding-right: 4px;
|
||||
}
|
||||
@@ -410,111 +430,165 @@ input::-webkit-inner-spin-button {
|
||||
}*/
|
||||
|
||||
.clause-table-field[readonly] {
|
||||
background-color: #EEEEF2;
|
||||
/*[{plugin-table-header-background-color}]*/
|
||||
border: 1px solid #CCCEDB;
|
||||
/*[{plugin-table-border-color}]*/
|
||||
background-color: #eeeef2;
|
||||
/*[{plugin-table-header-background-color}]*/
|
||||
border: 1px solid #cccedb;
|
||||
/*[{plugin-table-border-color}]*/
|
||||
}
|
||||
|
||||
.addClause-title {
|
||||
/*[{common-common-controls-button-border-hover}]*/
|
||||
cursor: pointer;
|
||||
margin-left: -5px;
|
||||
/*[{common-common-controls-button-border-hover}]*/
|
||||
cursor: pointer;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.addClause {
|
||||
width: 125px;
|
||||
padding: 8px 0px 5px 5px;
|
||||
border: 1px solid #fff;
|
||||
margin-left: 5px;
|
||||
width: 125px;
|
||||
padding: 8px 0px 5px 5px;
|
||||
border: 1px solid #fff;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.addClause:hover {
|
||||
.hover();
|
||||
.hover();
|
||||
}
|
||||
|
||||
.addClause:active {
|
||||
.active();
|
||||
border: 1px dashed @AccentMedium;
|
||||
.active();
|
||||
border: 1px dashed @AccentMedium;
|
||||
}
|
||||
|
||||
.clause-table-row {
|
||||
min-width: 550px;
|
||||
width: 100%;
|
||||
min-width: 550px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clause-table-field field-column {
|
||||
min-width: 75px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
min-width: 75px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clause-table-field field-input {
|
||||
min-width: 54px;
|
||||
margin-left: -78px;
|
||||
height: 25px;
|
||||
border: none;
|
||||
min-width: 54px;
|
||||
margin-left: -78px;
|
||||
height: 25px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.query-panel .row .spacing {
|
||||
padding-bottom: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.query-panel .divider.horizontal {
|
||||
height: 10px;
|
||||
width: 100%
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inline-div {
|
||||
display: inline
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.querybuilder-addpropertyImg,
|
||||
.querybuilder-addpropertyImg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 3px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.querybuilder-cancelImg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: 3px;
|
||||
margin-bottom: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: 3px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.addclauseProperty-Img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 12px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.entity-Add-Cancel {
|
||||
padding: @DefaultSpace @SmallSpace @SmallSpace;
|
||||
cursor: pointer;
|
||||
padding: @DefaultSpace @SmallSpace @SmallSpace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.entity-Add-Cancel:hover {
|
||||
.hover();
|
||||
.hover();
|
||||
}
|
||||
|
||||
.entity-Add-Cancel:active {
|
||||
.active();
|
||||
.active();
|
||||
}
|
||||
|
||||
.query-builder-isDisabled {
|
||||
border: 1px solid #CCCEDB;
|
||||
color: #ccc;
|
||||
border: 1px solid #cccedb;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.edit-value-text {
|
||||
padding-left: @DefaultSpace;
|
||||
padding-left: @DefaultSpace;
|
||||
}
|
||||
|
||||
.expand-triangle {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.expand-triangle-right {
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.query-document-detail-list {
|
||||
height: 100%;
|
||||
}
|
||||
.query-table-clause-container {
|
||||
max-height: 150px;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.query-tab-document-pagination {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding-left: 12px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
}
|
||||
.pagination {
|
||||
margin: 15px 0 !important;
|
||||
order: 2;
|
||||
padding-right: 15px;
|
||||
li > .item-link {
|
||||
position: relative;
|
||||
float: left;
|
||||
padding: 6px 12px;
|
||||
margin-left: -1px;
|
||||
line-height: 1.42857143;
|
||||
text-decoration: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
background: #eef7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.noData {
|
||||
background-color: #e3e2e6;
|
||||
color: #e3e2e6;
|
||||
padding-top: 1px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
@media only screen and (max-width: 1200px) {
|
||||
@@ -524,4 +598,4 @@ input::-webkit-inner-spin-button {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}*/
|
||||
}*/
|
||||
|
||||
34145
package-lock.json
generated
34145
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ReactBindingHandler from "./ReactBindingHandler";
|
||||
import "../Explorer/Tables/DataTable/DataTableBindingManager";
|
||||
|
||||
export class BindingHandlersRegisterer {
|
||||
public static registerBindingHandlers() {
|
||||
ko.bindingHandlers.setTemplateReady = {
|
||||
|
||||
384
src/Common/Constants.ts
Normal file
384
src/Common/Constants.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
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";
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export const Spark = "spark-public-preview";
|
||||
export const Notebooks = "sparknotebooks-public-preview";
|
||||
export const StorageAnalytics = "storageanalytics-public-preview";
|
||||
@@ -1,3 +0,0 @@
|
||||
export const Days90 = 7776000;
|
||||
export const Infinite = -1;
|
||||
export const Disabled = 0;
|
||||
@@ -1,2 +0,0 @@
|
||||
export const runtimeProxy = "/api/RuntimeProxy";
|
||||
export const guestRuntimeProxy = "/api/guest/RuntimeProxy";
|
||||
@@ -1,6 +0,0 @@
|
||||
// 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;
|
||||
@@ -1,5 +0,0 @@
|
||||
export const ResourceTree = "Resource Tree";
|
||||
export const ContextualPane = "Contextual Pane";
|
||||
export const Tab = "Tab";
|
||||
export const ShareDialog = "Share Access Dialog";
|
||||
export const Notebook = "Notebook";
|
||||
@@ -1,6 +0,0 @@
|
||||
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";
|
||||
@@ -1,2 +0,0 @@
|
||||
export const notebookWorkspaces = "Microsoft.DocumentDB/databaseAccounts/notebookWorkspaces";
|
||||
export const synapseWorkspaces = "Microsoft.Synapse/workspaces";
|
||||
@@ -1,5 +0,0 @@
|
||||
export const partitionKeyKind = "Hash";
|
||||
export const singlePartitionStorageInGb = "10";
|
||||
export const multiPartitionStorageInGb = "100";
|
||||
export const maxChangeFeedRetentionDuration = 10;
|
||||
export const partitionKeyVersion = 2;
|
||||
@@ -1,8 +0,0 @@
|
||||
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";
|
||||
@@ -1,9 +0,0 @@
|
||||
// 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";
|
||||
@@ -1,9 +0,0 @@
|
||||
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;
|
||||
@@ -1,3 +0,0 @@
|
||||
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";
|
||||
@@ -1 +0,0 @@
|
||||
export const IsBsonSchemaV2 = "true";
|
||||
@@ -1 +0,0 @@
|
||||
export const Url = "https://aka.ms/cosmosdbanalytics";
|
||||
@@ -1,8 +0,0 @@
|
||||
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;
|
||||
@@ -1,8 +0,0 @@
|
||||
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";
|
||||
@@ -1,8 +0,0 @@
|
||||
// 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";
|
||||
@@ -1,40 +0,0 @@
|
||||
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
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
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";
|
||||
@@ -1,23 +0,0 @@
|
||||
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,
|
||||
];
|
||||
@@ -1,8 +0,0 @@
|
||||
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;
|
||||
@@ -1,2 +0,0 @@
|
||||
export const protocol = "https";
|
||||
export const defaultPort = "10255";
|
||||
@@ -1,8 +0,0 @@
|
||||
// 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";
|
||||
@@ -1,27 +0,0 @@
|
||||
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.";
|
||||
@@ -1,2 +0,0 @@
|
||||
export const V1 = "V1";
|
||||
export const V2 = "V2";
|
||||
@@ -1,8 +0,0 @@
|
||||
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;
|
||||
@@ -1,4 +0,0 @@
|
||||
export const CollectionName = "___Query";
|
||||
export const DatabaseName = "___Cosmos";
|
||||
export const OfferThroughput = 400;
|
||||
export const PartitionKeyProperty = "id";
|
||||
@@ -1,6 +0,0 @@
|
||||
export const localhost = "localhost";
|
||||
export const blackforest = "blackforest";
|
||||
export const fairfax = "fairfax";
|
||||
export const mooncake = "mooncake";
|
||||
export const productionPortal = "prod";
|
||||
export const dev = "dev";
|
||||
@@ -1,2 +0,0 @@
|
||||
export const nameMinLength = 3;
|
||||
export const nameMaxLength = 63;
|
||||
@@ -1 +0,0 @@
|
||||
export const defaultExperience = "defaultExperience";
|
||||
@@ -1,5 +0,0 @@
|
||||
export const Terminal = "terminal";
|
||||
export const Server = "server";
|
||||
export const Token = "token";
|
||||
export const SubscriptionId = "subscriptionId";
|
||||
export const TerminalEndpoint = "terminalEndpoint";
|
||||
@@ -1,5 +0,0 @@
|
||||
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;
|
||||
@@ -1,4 +0,0 @@
|
||||
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";
|
||||
@@ -1,105 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
<TextField
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
className="addEntityTextField"
|
||||
id="entityValueId"
|
||||
// id="entityValueId"
|
||||
autoFocus
|
||||
disabled={isEntityValueDisable}
|
||||
type={entityValueType}
|
||||
|
||||
@@ -80,7 +80,8 @@ export function queryDocuments(
|
||||
};
|
||||
|
||||
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
|
||||
const headers: HeadersInit = {
|
||||
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
...authHeaders(),
|
||||
[CosmosSDKConstants.HttpHeaders.IsQuery]: "true",
|
||||
|
||||
@@ -95,7 +95,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
<Stack horizontal tokens={sectionStackTokens}>
|
||||
<TextField
|
||||
label={entityPropertyLabel && entityPropertyLabel}
|
||||
id="entityPropertyId"
|
||||
autoFocus
|
||||
disabled={isPropertyTypeDisable}
|
||||
placeholder={entityPropertyPlaceHolder}
|
||||
@@ -109,7 +108,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
onChange={onEntityTypeChange}
|
||||
options={options}
|
||||
disabled={isPropertyTypeDisable}
|
||||
id="entityTypeId"
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
<EntityValue
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Link,
|
||||
PrimaryButton,
|
||||
ProgressIndicator,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import React, { FC } from "react";
|
||||
@@ -31,7 +30,6 @@ export interface DialogState {
|
||||
onOk: () => void,
|
||||
cancelLabel: string,
|
||||
onCancel: () => void,
|
||||
contentHtml?: JSX.Element,
|
||||
choiceGroupProps?: IChoiceGroupProps,
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean
|
||||
@@ -60,7 +58,6 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
onOk: () => void,
|
||||
cancelLabel: string,
|
||||
onCancel: () => void,
|
||||
contentHtml?: JSX.Element,
|
||||
choiceGroupProps?: IChoiceGroupProps,
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean
|
||||
@@ -79,7 +76,6 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
get().closeDialog();
|
||||
onCancel && onCancel();
|
||||
},
|
||||
contentHtml,
|
||||
choiceGroupProps,
|
||||
textFieldProps,
|
||||
primaryButtonDisabled,
|
||||
@@ -128,7 +124,6 @@ export interface DialogProps {
|
||||
type?: DialogType;
|
||||
showCloseButton?: boolean;
|
||||
onDismiss?: () => void;
|
||||
contentHtml?: JSX.Element;
|
||||
}
|
||||
|
||||
const DIALOG_MIN_WIDTH = "400px";
|
||||
@@ -155,7 +150,6 @@ export const Dialog: FC = () => {
|
||||
type,
|
||||
showCloseButton,
|
||||
onDismiss,
|
||||
contentHtml,
|
||||
} = props || {};
|
||||
|
||||
const dialogProps: IDialogProps = {
|
||||
@@ -197,7 +191,6 @@ export const Dialog: FC = () => {
|
||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
)}
|
||||
{contentHtml && <Text>{contentHtml}</Text>}
|
||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||
<DialogFooter>
|
||||
<PrimaryButton {...primaryButtonProps} />
|
||||
|
||||
@@ -17,8 +17,6 @@ import Explorer from "../../Explorer";
|
||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
import "./NotebookViewerComponent.less";
|
||||
@@ -148,9 +146,7 @@ export class NotebookViewerComponent
|
||||
<NotebookMetadataComponent
|
||||
data={this.state.galleryItem}
|
||||
isFavorite={this.state.isFavorite}
|
||||
downloadButtonText={
|
||||
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
|
||||
}
|
||||
downloadButtonText={this.props.container && "Download to my notebooks"}
|
||||
onTagClick={this.props.onTagClick}
|
||||
onFavoriteClick={this.favoriteItem}
|
||||
onUnfavoriteClick={this.unfavoriteItem}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Link } from "@fluentui/react/lib/Link";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import _ from "underscore";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||
@@ -13,7 +11,6 @@ import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHand
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { QueriesClient } from "../Common/QueriesClient";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
@@ -166,10 +163,23 @@ export default class Explorer {
|
||||
|
||||
useNotebook.subscribe(
|
||||
async () => {
|
||||
this.initiateAndRefreshNotebookList();
|
||||
useNotebook.getState().setIsRefreshed(false);
|
||||
if (!this.notebookManager) {
|
||||
const NotebookManager = await (
|
||||
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.isRefreshed
|
||||
(state) => state.isNotebookEnabled
|
||||
);
|
||||
|
||||
this.resourceTree = new ResourceTreeAdapter(this);
|
||||
@@ -202,23 +212,6 @@ export default class Explorer {
|
||||
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 {
|
||||
const addSynapseLinkDialogProps: DialogProps = {
|
||||
linkProps: {
|
||||
@@ -352,7 +345,23 @@ export default class Explorer {
|
||||
return;
|
||||
}
|
||||
this._isInitializingNotebooks = true;
|
||||
if (userContext.features.phoenix === false) {
|
||||
if (userContext.features.phoenix) {
|
||||
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();
|
||||
const connectionInfo = await listConnectionInfo(
|
||||
userContext.subscriptionId,
|
||||
@@ -367,59 +376,13 @@ export default class Explorer {
|
||||
});
|
||||
}
|
||||
|
||||
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
||||
|
||||
this.refreshNotebookList();
|
||||
|
||||
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 {
|
||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||
handleError(
|
||||
@@ -691,9 +654,6 @@ export default class Explorer {
|
||||
if (!notebookContentItem || !notebookContentItem.path) {
|
||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||
}
|
||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
|
||||
this.allocateContainer();
|
||||
}
|
||||
|
||||
const notebookTabs = useTabs
|
||||
.getState()
|
||||
@@ -915,51 +875,9 @@ export default class Explorer {
|
||||
handleError(error, "Explorer/onNewNotebookClicked");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private getNewNoteWarningText(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<p>{Notebook.newNotebookModalContent1}</p>
|
||||
<br />
|
||||
<p>
|
||||
{Notebook.newNotebookModalContent2}
|
||||
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
|
||||
{Notebook.learnMore}
|
||||
</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
|
||||
private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void {
|
||||
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
@@ -1006,26 +924,7 @@ export default class Explorer {
|
||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||
}
|
||||
|
||||
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 {
|
||||
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||
let title: string;
|
||||
|
||||
switch (kind) {
|
||||
@@ -1076,7 +975,7 @@ export default class Explorer {
|
||||
notebookUrl?: string,
|
||||
galleryItem?: IGalleryItem,
|
||||
isFavorite?: boolean
|
||||
): Promise<void> {
|
||||
) {
|
||||
const title = "Gallery";
|
||||
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
||||
const galleryTab = useTabs
|
||||
@@ -1180,27 +1079,7 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||
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 {
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
@@ -1209,24 +1088,6 @@ 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> {
|
||||
userContext.authType === AuthType.ResourceToken
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
|
||||
@@ -736,7 +736,7 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
.on("dblclick", function (this: Element, _: MouseEvent, d: D3Node) {
|
||||
// https://stackoverflow.com/a/41945742 ('this' implicitly has type 'any' because it does not have a type annotation)
|
||||
// this is the <g> element
|
||||
self.onNodeClicked(this.parentNode, d);
|
||||
return self.onNodeClicked(this.parentNode, d);
|
||||
})
|
||||
.on("click", function (this: Element, _: MouseEvent, d: D3Node) {
|
||||
// this is the <g> element
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import * as Q from "q";
|
||||
import * as React from "react";
|
||||
@@ -296,6 +294,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.setGremlinParams();
|
||||
}
|
||||
|
||||
const selectedNode = this.state.highlightedNode;
|
||||
|
||||
props.onGraphAccessorCreated({
|
||||
applyFilter: this.submitQuery.bind(this),
|
||||
addVertex: this.addVertex.bind(this),
|
||||
@@ -303,7 +303,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
});
|
||||
} // constructor
|
||||
|
||||
public shareIGraphConfig(igraphConfig: IGraphConfig): void {
|
||||
public shareIGraphConfig(igraphConfig: IGraphConfig) {
|
||||
this.setState({
|
||||
igraphConfig: { ...igraphConfig },
|
||||
});
|
||||
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
||||
|
||||
// aggregate all the properties, remove dropped ones
|
||||
const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||
|
||||
// Compose the query
|
||||
const pkId = editedProperties.pkId;
|
||||
let pkId = editedProperties.pkId;
|
||||
let updateQueryFragment = "";
|
||||
|
||||
finalProperties.forEach((p) => {
|
||||
@@ -422,7 +422,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Called from ko binding
|
||||
* @param id
|
||||
*/
|
||||
public selectNode(id: string): void {
|
||||
public selectNode(id: string) {
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
|
||||
return;
|
||||
@@ -431,7 +431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.d3ForceGraph.selectNode(id);
|
||||
}
|
||||
|
||||
public deleteHighlightedNode(): void {
|
||||
public deleteHighlightedNode() {
|
||||
if (!this.state.highlightedNode) {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
|
||||
return;
|
||||
@@ -467,23 +467,23 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Is of type: {e: GremlinEdge, v: GremlinVertex}[]
|
||||
* @param data
|
||||
*/
|
||||
public static isEdgeVertexPairArray(data: any): boolean {
|
||||
public static isEdgeVertexPairArray(data: any) {
|
||||
if (!(data instanceof Array)) {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
|
||||
return false;
|
||||
}
|
||||
|
||||
const pairs: any[] = data;
|
||||
let pairs: any[] = data;
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
const item = pairs[i];
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(item, "e") ||
|
||||
!Object.prototype.hasOwnProperty.call(item, "v") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
||||
!item.hasOwnProperty("e") ||
|
||||
!item.hasOwnProperty("v") ||
|
||||
!item["e"].hasOwnProperty("id") ||
|
||||
!item["e"].hasOwnProperty("type") ||
|
||||
item["e"].type !== "edge" ||
|
||||
!Object.prototype.hasOwnProperty.call(item["v"], "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
||||
!item["v"].hasOwnProperty("id") ||
|
||||
!item["v"].hasOwnProperty("type") ||
|
||||
item["v"].type !== "vertex"
|
||||
) {
|
||||
return false;
|
||||
@@ -514,7 +514,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// Try hitting cache first
|
||||
const cache = outE ? this.outECache : this.inECache;
|
||||
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}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
|
||||
return Q.resolve(pairs);
|
||||
@@ -588,6 +588,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
vertex._outEAllLoaded &&
|
||||
vertex._inEAllLoaded
|
||||
) {
|
||||
console.info("No more edges to load for vertex " + vertex.id);
|
||||
updateGraphData();
|
||||
return Q.resolve(graphData);
|
||||
}
|
||||
@@ -667,7 +668,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}
|
||||
);
|
||||
|
||||
return promise.then(() => {
|
||||
return promise.then((nbPairsFetched: number) => {
|
||||
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
|
||||
vertex._pagination = {
|
||||
total:
|
||||
@@ -753,7 +754,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Create a new edge in docdb and update graph
|
||||
* @param e
|
||||
*/
|
||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<unknown> {
|
||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<any> {
|
||||
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
|
||||
e.label
|
||||
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
|
||||
@@ -771,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const edge = edges[0];
|
||||
const graphData = this.originalGraphData;
|
||||
let edge = edges[0];
|
||||
let graphData = this.originalGraphData;
|
||||
graphData.addEdge(edge);
|
||||
|
||||
// Allow loadNeighbors to load list new edge
|
||||
@@ -799,10 +800,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Manually update in-memory graph.
|
||||
* @param edgeId
|
||||
*/
|
||||
public removeEdge(edgeId: string): Q.Promise<unknown> {
|
||||
public removeEdge(edgeId: string): Q.Promise<any> {
|
||||
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
||||
() => {
|
||||
const graphData = this.originalGraphData;
|
||||
let graphData = this.originalGraphData;
|
||||
graphData.removeEdge(edgeId, false);
|
||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||
},
|
||||
@@ -825,14 +826,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return false;
|
||||
}
|
||||
|
||||
const vertices: any[] = data;
|
||||
let vertices: any[] = data;
|
||||
if (vertices.length > 0) {
|
||||
const v0 = vertices[0];
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(v0, "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(v0, "type") ||
|
||||
v0.type !== "vertex"
|
||||
) {
|
||||
let v0 = vertices[0];
|
||||
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -840,7 +837,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}
|
||||
|
||||
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
|
||||
const data = result.data as GraphData.GremlinVertex[];
|
||||
const data = result.data as any;
|
||||
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
|
||||
|
||||
if (data === null) {
|
||||
@@ -930,13 +927,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
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)";
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
|
||||
throw { title: err };
|
||||
}
|
||||
|
||||
const vertex = vertices[0];
|
||||
let vertex = vertices[0];
|
||||
const graphData = this.originalGraphData;
|
||||
graphData.addVertex(vertex);
|
||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||
@@ -1025,7 +1022,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.gremlinClient.destroy();
|
||||
}
|
||||
public componentDidMount(): void {
|
||||
if (this.props.onLoadStartKey !== null && this.props.onLoadStartKey !== undefined) {
|
||||
if (this.props.onLoadStartKey != null && this.props.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.Tab,
|
||||
{
|
||||
@@ -1085,7 +1082,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.Error, msg: string, ...errorData: any[]): void;
|
||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||
let errorDataStr = "";
|
||||
let errorDataStr: string = "";
|
||||
if (errorData && errorData.length > 0) {
|
||||
console.error(msg, errorData);
|
||||
errorDataStr = ": " + JSON.stringify(errorData);
|
||||
@@ -1164,15 +1161,12 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
)}"`
|
||||
).then(
|
||||
(documents: DataModels.DocumentId[]) => {
|
||||
$.each(
|
||||
documents,
|
||||
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
|
||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||
data: doc["icon"],
|
||||
format: doc["format"],
|
||||
};
|
||||
}
|
||||
);
|
||||
$.each(documents, (index: number, doc: any) => {
|
||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||
data: doc["icon"],
|
||||
format: doc["format"],
|
||||
};
|
||||
});
|
||||
|
||||
// Update graph configuration
|
||||
this.setState({
|
||||
@@ -1229,8 +1223,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
const key = this.state.igraphConfig.nodeCaption;
|
||||
return $.map(
|
||||
this.state.rootMap,
|
||||
(value: any): LeftPane.CaptionId => {
|
||||
const result = GraphData.GraphData.getNodePropValue(value, key);
|
||||
(value: any, index: number): LeftPane.CaptionId => {
|
||||
let result = GraphData.GraphData.getNodePropValue(value, key);
|
||||
return {
|
||||
caption: result !== undefined ? result : value.id,
|
||||
id: value.id,
|
||||
@@ -1243,7 +1237,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Selecting a root node means
|
||||
* @param node
|
||||
*/
|
||||
private selectRootNode(id: string): Q.Promise<unknown> {
|
||||
private selectRootNode(id: string): Q.Promise<any> {
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
|
||||
} else {
|
||||
@@ -1288,7 +1282,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.collectNodeProperties(this.originalGraphData.vertices);
|
||||
this.updatePropertiesPane(id);
|
||||
},
|
||||
(reason: string) => {
|
||||
(reason: any) => {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
|
||||
}
|
||||
);
|
||||
@@ -1355,10 +1349,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
|
||||
if (
|
||||
this.props.collectionPartitionKeyProperty &&
|
||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty) &&
|
||||
v.hasOwnProperty("properties") &&
|
||||
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) &&
|
||||
v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties[this.props.collectionPartitionKeyProperty][0], "value")
|
||||
v.properties[this.props.collectionPartitionKeyProperty][0].hasOwnProperty("value")
|
||||
) {
|
||||
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
|
||||
return GraphExplorer.generatePkIdPair(pk, v.id);
|
||||
@@ -1376,8 +1370,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
|
||||
if (
|
||||
this.props.collectionPartitionKeyProperty &&
|
||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty)
|
||||
v.hasOwnProperty("properties") &&
|
||||
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty)
|
||||
) {
|
||||
const pk = v.properties[this.props.collectionPartitionKeyProperty];
|
||||
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
|
||||
@@ -1394,14 +1388,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* @return id
|
||||
*/
|
||||
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
||||
const { id } = d;
|
||||
let { id } = d;
|
||||
if (typeof id !== "string") {
|
||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||
logConsoleError(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (collectionPartitionKeyProperty && Object.prototype.hasOwnProperty.call(d, collectionPartitionKeyProperty)) {
|
||||
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
||||
if (Array.isArray(pk) && pk.length > 0) {
|
||||
@@ -1431,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
||||
return this.executeNonPagedDocDbQuery(q).then(
|
||||
(documents: DataModels.DocumentId[]) => {
|
||||
const possibleVertices = [] as PossibleVertex[];
|
||||
let possibleVertices = [] as PossibleVertex[];
|
||||
$.each(documents, (index: number, item: any) => {
|
||||
if (highlightedNodeId && item.id === highlightedNodeId) {
|
||||
// Exclude highlighed node in the list
|
||||
@@ -1445,7 +1439,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
caption: item.p,
|
||||
});
|
||||
} else {
|
||||
if (Object.prototype.hasOwnProperty.call(item, "p")) {
|
||||
if (item.hasOwnProperty("p")) {
|
||||
possibleVertices.push({
|
||||
value: item.id,
|
||||
caption: item.p[0]["_value"],
|
||||
@@ -1468,17 +1462,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* @param addedEdges
|
||||
* @return promise when done
|
||||
*/
|
||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<unknown> {
|
||||
const promises = [];
|
||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
||||
let promises = [];
|
||||
// Drop edges
|
||||
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
||||
const id = editedEdges.droppedIds[i];
|
||||
let id = editedEdges.droppedIds[i];
|
||||
promises.push(this.removeEdge(id));
|
||||
}
|
||||
|
||||
// Add edges
|
||||
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
||||
const e = editedEdges.addedEdges[i];
|
||||
let e = editedEdges.addedEdges[i];
|
||||
promises.push(
|
||||
this.createNewEdge(e).then(() => {
|
||||
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
||||
@@ -1531,9 +1525,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
/**
|
||||
* For unit testing purposes
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onGraphUpdated(_timestamp: number): void {}
|
||||
public onGraphUpdated(timestamp: number): void {}
|
||||
|
||||
/**
|
||||
* Get node properties for styling purposes. Result is the union of all properties of all nodes.
|
||||
@@ -1541,17 +1533,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
||||
const props = {} as any; // Hashset
|
||||
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
||||
for (const p in item) {
|
||||
for (var p in item) {
|
||||
// DocDB: Exclude type because it's always 'vertex'
|
||||
if (p !== "type" && typeof (item as any)[p] === "string") {
|
||||
props[p] = true;
|
||||
}
|
||||
}
|
||||
// Inspect properties
|
||||
if (Object.prototype.hasOwnProperty.call(item, "properties")) {
|
||||
if (item.hasOwnProperty("properties")) {
|
||||
// TODO This is DocDB-graph specific
|
||||
// Assume each property value is [{value:... }]
|
||||
for (const f in item.properties) {
|
||||
for (var f in item.properties) {
|
||||
props[f] = true;
|
||||
}
|
||||
}
|
||||
@@ -1578,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.originalGraphData.getVertexById(id);
|
||||
let data = this.originalGraphData.getVertexById(id);
|
||||
|
||||
// A bit of translation to make it easier to display
|
||||
const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||
for (const p in data.properties) {
|
||||
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||
for (let p in data.properties) {
|
||||
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
||||
}
|
||||
|
||||
// update neighbors
|
||||
const sources: NeighborVertexBasicInfo[] = [];
|
||||
const targets: NeighborVertexBasicInfo[] = [];
|
||||
let sources: NeighborVertexBasicInfo[] = [];
|
||||
let targets: NeighborVertexBasicInfo[] = [];
|
||||
this.props.onResetDefaultGraphConfigValues();
|
||||
const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
||||
const sData: GraphHighlightedNodeData = {
|
||||
let sData: GraphHighlightedNodeData = {
|
||||
id: data.id,
|
||||
label: data.label,
|
||||
properties: props,
|
||||
@@ -1619,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
targets: NeighborVertexBasicInfo[]
|
||||
): void {
|
||||
// update neighbors
|
||||
const gd = this.originalGraphData;
|
||||
const v = gd.getVertexById(id);
|
||||
let gd = this.originalGraphData;
|
||||
let v = gd.getVertexById(id);
|
||||
|
||||
// Clear the array while keeping the references
|
||||
sources.length = 0;
|
||||
targets.length = 0;
|
||||
|
||||
const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||
|
||||
for (const p in v.inE) {
|
||||
for (let p in v.inE) {
|
||||
possibleEdgeLabels[p] = true;
|
||||
const edges = v.inE[p];
|
||||
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
||||
@@ -1637,7 +1629,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
|
||||
return;
|
||||
}
|
||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
sources.push({
|
||||
name: caption,
|
||||
id: neighborId,
|
||||
@@ -1647,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
});
|
||||
}
|
||||
|
||||
for (const p in v.outE) {
|
||||
for (let p in v.outE) {
|
||||
possibleEdgeLabels[p] = true;
|
||||
const edges = v.outE[p];
|
||||
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
||||
@@ -1656,7 +1648,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
|
||||
return;
|
||||
}
|
||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
targets.push({
|
||||
name: caption,
|
||||
id: neighborId,
|
||||
@@ -1668,7 +1660,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
this.setState({
|
||||
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
|
||||
(value: string): InputTypeaheadComponent.Item => {
|
||||
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => {
|
||||
return { caption: value, value: value };
|
||||
}
|
||||
),
|
||||
@@ -1689,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedVertex = vertices[0];
|
||||
let updatedVertex = vertices[0];
|
||||
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
||||
const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||
// Copy updated properties
|
||||
if (Object.prototype.hasOwnProperty.call(currentVertex, "properties")) {
|
||||
if (currentVertex.hasOwnProperty("properties")) {
|
||||
delete currentVertex["properties"];
|
||||
}
|
||||
for (const p in updatedVertex) {
|
||||
for (var p in updatedVertex) {
|
||||
(currentVertex as any)[p] = updatedVertex[p];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
||||
const hn = this.state.highlightedNode;
|
||||
let hn = this.state.highlightedNode;
|
||||
if (hn && hn.id === updatedVertex.id) {
|
||||
this.updatePropertiesPane(hn.id);
|
||||
}
|
||||
@@ -1716,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
igraphConfig?: IGraphConfig
|
||||
) {
|
||||
this.originalGraphData = graphData;
|
||||
const gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||
let gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
||||
return;
|
||||
@@ -1881,7 +1873,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
promise
|
||||
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
||||
.catch((error: Error) => {
|
||||
.catch((error: any) => {
|
||||
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useTabs } from "../../../hooks/useTabs";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
import * as CommandBarUtil from "./CommandBarUtil";
|
||||
@@ -56,15 +55,15 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||
}
|
||||
|
||||
if (
|
||||
userContext.features.phoenix === false &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
userContext.features.phoenix === true &&
|
||||
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||
) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
||||
}
|
||||
|
||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||
}
|
||||
|
||||
|
||||
@@ -307,18 +307,11 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
|
||||
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||
const label = "New " + getDatabaseName();
|
||||
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||
|
||||
return {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"New " + getDatabaseName(),
|
||||
<AddDatabasePanel explorer={container} buttonElement={newDatabaseButton} />
|
||||
),
|
||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -603,7 +596,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
||||
return {
|
||||
iconSrc: GitHubIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
@@ -613,8 +606,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||
junoClientProp={junoClient}
|
||||
/>
|
||||
);
|
||||
},
|
||||
),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { StyleConstants } from "../../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||
|
||||
@@ -204,9 +203,9 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
||||
export const createConnectionStatus = (key: string): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <ConnectionStatus container={container} />,
|
||||
onRender: () => <ConnectionStatus />,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,182 +3,77 @@
|
||||
.connectionStatusContainer {
|
||||
cursor: default;
|
||||
align-items: center;
|
||||
margin: 0 9px;
|
||||
border: 1px;
|
||||
min-height: 44px;
|
||||
|
||||
> span {
|
||||
padding-right: 12px;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
font-family: @DataExplorerFont;
|
||||
color: @DefaultFontColor;
|
||||
}
|
||||
&:focus{
|
||||
outline: 0px;
|
||||
}
|
||||
}
|
||||
.commandReactBtn {
|
||||
&:hover {
|
||||
background-color: rgb(238, 247, 255);
|
||||
color: rgb(32, 31, 30);
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus{
|
||||
outline: 1px dashed #605e5c;
|
||||
}
|
||||
.connectionStatusFailed{
|
||||
color: #bd1919;
|
||||
}
|
||||
.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 {
|
||||
.ring-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
font-size: 9px!important;
|
||||
padding: 0px!important;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.status::before,
|
||||
.status::after {
|
||||
}
|
||||
|
||||
.ringringGreen {
|
||||
border: 3px solid green;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.status::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
background-color: rgba(#fff, 0.1);
|
||||
border-radius: 100%;
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale(0);
|
||||
}
|
||||
|
||||
.connected{
|
||||
background-color: green;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(green, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
.connecting{
|
||||
background-color:#ffbf00;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(#ffbf00, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
.failed{
|
||||
background-color:#bd1919;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(#bd1919, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
|
||||
.status.connecting.is-animating {
|
||||
animation: status-outer-connecting 3000ms infinite;
|
||||
}
|
||||
.status.failed.is-animating {
|
||||
animation: status-outer-failed 3000ms infinite;
|
||||
}
|
||||
.status.connected.is-animating {
|
||||
animation: status-outer-connected 3000ms infinite;
|
||||
}
|
||||
@keyframes status-outer-connected {
|
||||
|
||||
0% {
|
||||
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);
|
||||
}
|
||||
}
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
.ringringYellow{
|
||||
border: 3px solid #ffbf00;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
.ringringRed{
|
||||
border: 3px solid #bd1919;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
@keyframes pulsate {
|
||||
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;}
|
||||
15% {opacity: 0.8;}
|
||||
25% {opacity: 0.6;}
|
||||
45% {opacity: 0.4;}
|
||||
70% {opacity: 0.3;}
|
||||
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;}
|
||||
}
|
||||
.locationGreenDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: green;
|
||||
}
|
||||
.locationYellowDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: #ffbf00;
|
||||
}
|
||||
.locationRedDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: #bd1919;
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||
import { ActionButton } from "@fluentui/react/lib/Button";
|
||||
import * as React from "react";
|
||||
import "../../../../less/hostedexplorer.less";
|
||||
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
interface Props {
|
||||
container: Explorer;
|
||||
}
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||
|
||||
export const ConnectionStatus: React.FC = (): JSX.Element => {
|
||||
const [second, setSecond] = React.useState("00");
|
||||
const [minute, setMinute] = React.useState("00");
|
||||
const [isActive, setIsActive] = React.useState(false);
|
||||
const [counter, setCounter] = React.useState(0);
|
||||
const [statusColor, setStatusColor] = React.useState("");
|
||||
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
||||
const [statusColor, setStatusColor] = React.useState("locationYellowDot");
|
||||
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow");
|
||||
const toolTipContent = "Hosted runtime status.";
|
||||
React.useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
|
||||
@@ -43,65 +39,34 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
};
|
||||
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||
|
||||
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) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||
setIsActive(true);
|
||||
setStatusColor("status connecting is-animating");
|
||||
setToolTipContent("Connecting to temporary workspace.");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("status connected is-animating");
|
||||
setToolTipContent("Connected to temporary workspace.");
|
||||
setStatusColor("locationGreenDot");
|
||||
setStatusColorAnimation("ringringGreen");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("status failed is-animating");
|
||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||
setStatusColor("locationRedDot");
|
||||
setStatusColorAnimation("ringringRed");
|
||||
}
|
||||
return (
|
||||
<ActionButton
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
}
|
||||
>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<i className={statusColor}></i>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<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>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<div className="ring-container">
|
||||
<div className={statusColorAnimation}></div>
|
||||
<Icon iconName="LocationDot" className={statusColor} />
|
||||
</div>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<ProgressIndicator description={minute + ":" + second} />
|
||||
)}
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||
// Vendor modules
|
||||
import {
|
||||
@@ -31,19 +30,6 @@ export interface NotebookComponentBootstrapperOptions {
|
||||
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 {
|
||||
public contentRef: ContentRef;
|
||||
protected renderExtraComponent: () => JSX.Element;
|
||||
@@ -55,7 +41,7 @@ export class NotebookComponentBootstrapper {
|
||||
this.contentRef = options.contentRef;
|
||||
}
|
||||
|
||||
protected static wrapModelIntoContent(name: string, path: string, content: unknown): IWrapModel {
|
||||
protected static wrapModelIntoContent(name: string, path: string, content: any) {
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
@@ -63,7 +49,7 @@ export class NotebookComponentBootstrapper {
|
||||
created: "",
|
||||
content,
|
||||
format: "json",
|
||||
mimetype: undefined,
|
||||
mimetype: null as any,
|
||||
size: 0,
|
||||
writeable: false,
|
||||
type: "notebook",
|
||||
@@ -99,7 +85,7 @@ export class NotebookComponentBootstrapper {
|
||||
};
|
||||
}
|
||||
|
||||
public setContent(name: string, content: unknown): void {
|
||||
public setContent(name: string, content: any): void {
|
||||
this.getStore().dispatch(
|
||||
actions.fetchContentFulfilled({
|
||||
filepath: undefined,
|
||||
@@ -284,6 +270,7 @@ export class NotebookComponentBootstrapper {
|
||||
public isContentDirty(): boolean {
|
||||
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
|
||||
if (!content) {
|
||||
console.log("No error");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
* Notebook container related stuff
|
||||
*/
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { ConnectionStatusType } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export class NotebookContainerClient {
|
||||
@@ -45,7 +42,7 @@ export class NotebookContainerClient {
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
@@ -78,12 +75,6 @@ export class NotebookContainerClient {
|
||||
freeKB: memoryUsageInfo.free,
|
||||
};
|
||||
}
|
||||
} else if (NotebookUtil.isPhoenixEnabled()) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.ReConnect,
|
||||
};
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(true);
|
||||
}
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
@@ -93,13 +84,6 @@ export class NotebookContainerClient {
|
||||
"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();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,6 @@ export default class NotebookManager {
|
||||
"Cancel",
|
||||
() => reject(new Error("Commit dialog canceled")),
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
label: "Commit message",
|
||||
autoAdjustHeight: true,
|
||||
|
||||
@@ -16,10 +16,9 @@ import "./NotebookReadOnlyRenderer.less";
|
||||
import SandboxOutputs from "./outputs/SandboxOutputs";
|
||||
|
||||
export interface NotebookRendererProps {
|
||||
contentRef: ContentRef;
|
||||
contentRef: any;
|
||||
hideInputs?: boolean;
|
||||
hidePrompts?: boolean;
|
||||
addTransform: (component: React.ComponentType & { MIMETYPE: string }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +27,7 @@ export interface NotebookRendererProps {
|
||||
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
componentDidMount() {
|
||||
if (!userContext.features.sandboxNotebookOutputs) {
|
||||
loadTransform(this.props as NotebookRendererProps);
|
||||
loadTransform(this.props as any);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
<div className="NotebookReadOnlyRender">
|
||||
<Cells contentRef={this.props.contentRef}>
|
||||
{{
|
||||
code: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<CodeCell id={id} contentRef={contentRef}>
|
||||
{{
|
||||
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
||||
@@ -74,14 +73,14 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
}}
|
||||
</CodeCell>
|
||||
),
|
||||
markdown: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
||||
{{
|
||||
editor: {},
|
||||
}}
|
||||
</MarkdownCell>
|
||||
),
|
||||
raw: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||
{{
|
||||
editor: {
|
||||
@@ -99,7 +98,6 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||
return {
|
||||
@@ -116,4 +114,4 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
|
||||
return mapDispatchToProps;
|
||||
};
|
||||
|
||||
export default connect(undefined, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||
export default connect(null, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { AppState, selectors } from "@nteract/core";
|
||||
import domtoimage from "dom-to-image";
|
||||
import Html2Canvas from "html2canvas";
|
||||
import path from "path";
|
||||
import { userContext } from "../../UserContext";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import * as StringUtils from "../../Utils/StringUtils";
|
||||
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||
@@ -329,16 +328,4 @@ export class NotebookUtil {
|
||||
link.click();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ import { cloneDeep } from "lodash";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { ConnectionStatusType } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -16,7 +14,6 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
import NotebookManager from "./NotebookManager";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
|
||||
interface NotebookState {
|
||||
isNotebookEnabled: boolean;
|
||||
@@ -31,10 +28,8 @@ interface NotebookState {
|
||||
myNotebooksContentRoot: NotebookContentItem;
|
||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
galleryContentRoot: NotebookContentItem;
|
||||
connectionInfo: ContainerConnectionInfo;
|
||||
connectionInfo: DataModels.ContainerConnectionInfo;
|
||||
notebookFolderName: string;
|
||||
isAllocating: boolean;
|
||||
isRefreshed: boolean;
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||
@@ -51,10 +46,7 @@ interface NotebookState {
|
||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||
setIsAllocating: (isAllocating: boolean) => void;
|
||||
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||
setIsRefreshed: (isAllocating: boolean) => void;
|
||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void;
|
||||
}
|
||||
|
||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
@@ -77,12 +69,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
myNotebooksContentRoot: undefined,
|
||||
gitHubNotebooksContentRoot: undefined,
|
||||
galleryContentRoot: undefined,
|
||||
connectionInfo: {
|
||||
status: ConnectionStatusType.Connect,
|
||||
},
|
||||
connectionInfo: undefined,
|
||||
notebookFolderName: undefined,
|
||||
isAllocating: false,
|
||||
isRefreshed: false,
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||
@@ -187,7 +175,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||
},
|
||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||
const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
|
||||
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks";
|
||||
set({ notebookFolderName });
|
||||
const myNotebooksContentRoot = {
|
||||
name: get().notebookFolderName,
|
||||
@@ -268,15 +256,5 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
set({ gitHubNotebooksContentRoot });
|
||||
}
|
||||
},
|
||||
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 }),
|
||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }),
|
||||
}));
|
||||
|
||||
@@ -13,21 +13,21 @@ import {
|
||||
Text,
|
||||
TooltipHost,
|
||||
} 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 { 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 * 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 { 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 { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -999,7 +999,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
const collectionId: string = this.state.collectionId.trim();
|
||||
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
|
||||
let partitionKeyString = this.state.isSharded ? this.state.partitionKey.trim() : undefined;
|
||||
let partitionKeyString = this.state.partitionKey.trim();
|
||||
|
||||
if (userContext.apiType === "Tables") {
|
||||
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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 * as SharedConstants from "Shared/Constants";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import * as SharedConstants from "../../../Shared/Constants";
|
||||
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 Explorer from "../../Explorer";
|
||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||
|
||||
@@ -77,7 +77,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
selectedLocation.repo
|
||||
)} - ${selectedLocation.branch}`;
|
||||
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||
destination = useNotebook.getState().notebookFolderName;
|
||||
destination = "My Notebooks Scratch";
|
||||
}
|
||||
|
||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
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 { 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 { 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 { 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 { useSelectedNode } from "../../useSelectedNode";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Text, TextField } from "@fluentui/react";
|
||||
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 { 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 { 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 { 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 { useSelectedNode } from "../useSelectedNode";
|
||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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 { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import * as StringUtility from "Shared/StringUtility";
|
||||
import { userContext } from "UserContext";
|
||||
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import { configContext } from "../../../ConfigContext";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
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";
|
||||
|
||||
export const SettingsPane: FunctionComponent = () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TextField } from "@fluentui/react";
|
||||
import * as ViewModels from "Contracts/ViewModels";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import React, { FormEvent, FunctionComponent, useState } from "react";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from "react";
|
||||
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
||||
import * as Entities from "../../Tables/Entities";
|
||||
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import { AddTableEntityPanel } from "./AddTableEntityPanel";
|
||||
|
||||
describe("Excute Add Table Entity Pane", () => {
|
||||
@@ -18,6 +18,8 @@ describe("Excute Add Table Entity Pane", () => {
|
||||
queryTablesTab: fakeQueryTablesTab,
|
||||
tableEntityListViewModel: fakeTableEntityListViewModel,
|
||||
cassandraApiClient: fakeCassandraApiClient,
|
||||
reloadEntities: () => "{}",
|
||||
headerItems: ["email"],
|
||||
};
|
||||
|
||||
it("should render Default properly", () => {
|
||||
@@ -27,13 +29,13 @@ describe("Excute Add Table Entity Pane", () => {
|
||||
|
||||
it("initially display 4 input field, 2 properties and 2 entity values", () => {
|
||||
const wrapper = mount(<AddTableEntityPanel {...props} />);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(0);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("add a new entity row", () => {
|
||||
const wrapper = mount(<AddTableEntityPanel {...props} />);
|
||||
wrapper.find(".addButtonEntiy").last().simulate("click");
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("remove a entity field", () => {
|
||||
@@ -41,6 +43,6 @@ describe("Excute Add Table Entity Pane", () => {
|
||||
// Since default entity row doesn't have delete option, so added row then delete for test cases.
|
||||
wrapper.find(".addButtonEntiy").last().simulate("click");
|
||||
wrapper.find("#deleteEntity").last().simulate("click");
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(0);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import * as _ from "underscore";
|
||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { TableEntity } from "../../../Common/TableEntity";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as TableConstants from "../../Tables/Constants";
|
||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||
import * as Entities from "../../Tables/Entities";
|
||||
import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient";
|
||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||
import * as Utilities from "../../Tables/Utilities";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
import {
|
||||
attributeNameLabel,
|
||||
@@ -36,9 +35,11 @@ import {
|
||||
|
||||
interface AddTableEntityPanelProps {
|
||||
tableDataClient: TableDataClient;
|
||||
queryTablesTab: QueryTablesTab;
|
||||
queryTablesTab: NewQueryTablesTab;
|
||||
tableEntityListViewModel: TableEntityListViewModel;
|
||||
cassandraApiClient: CassandraAPIDataClient;
|
||||
reloadEntities: () => void;
|
||||
headerItems: string[];
|
||||
}
|
||||
|
||||
interface EntityRowType {
|
||||
@@ -58,7 +59,10 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
queryTablesTab,
|
||||
tableEntityListViewModel,
|
||||
cassandraApiClient,
|
||||
reloadEntities,
|
||||
headerItems,
|
||||
}: AddTableEntityPanelProps): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||
@@ -76,7 +80,7 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
}, []);
|
||||
|
||||
const getDefaultEntitiesAttribute = async (): Promise<void> => {
|
||||
let headers = tableEntityListViewModel.headers;
|
||||
let headers = tableEntityListViewModel.headers?.length > 1 ? tableEntityListViewModel.headers : headerItems;
|
||||
if (DataTableUtilities.checkForDefaultHeader(headers)) {
|
||||
headers = [];
|
||||
if (userContext.apiType === "Tables") {
|
||||
@@ -116,47 +120,19 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
||||
try {
|
||||
await tableEntityListViewModel.addEntityToCache(newEntity);
|
||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||
tableEntityListViewModel.redrawTableThrottled();
|
||||
}
|
||||
reloadEntities();
|
||||
setFormError("");
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setFormError(errorMessage);
|
||||
handleError(errorMessage, "AddTableRow");
|
||||
throw error;
|
||||
} finally {
|
||||
closeSidePanel();
|
||||
setIsExecuting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
||||
let newHeaders: string[] = [];
|
||||
const keys = Object.keys(newEntity);
|
||||
keys &&
|
||||
keys.forEach((key: string) => {
|
||||
if (
|
||||
!_.contains(viewModel.headers, key) &&
|
||||
key !== TableEntityProcessor.keyProperties.attachments &&
|
||||
key !== TableEntityProcessor.keyProperties.etag &&
|
||||
key !== TableEntityProcessor.keyProperties.resourceId &&
|
||||
key !== TableEntityProcessor.keyProperties.self &&
|
||||
(!(userContext.apiType === "Cassandra") || key !== TableConstants.EntityKeyNames.RowKey)
|
||||
) {
|
||||
newHeaders.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
let newHeadersInserted = false;
|
||||
if (newHeaders.length) {
|
||||
if (!DataTableUtilities.checkForDefaultHeader(viewModel.headers)) {
|
||||
newHeaders = viewModel.headers.concat(newHeaders);
|
||||
}
|
||||
viewModel.updateHeaders(newHeaders, /* notifyColumnChanges */ true, /* enablePrompt */ false);
|
||||
newHeadersInserted = true;
|
||||
}
|
||||
return newHeadersInserted;
|
||||
};
|
||||
|
||||
/* Add new entity row */
|
||||
const addNewEntity = (): void => {
|
||||
const cloneEntities: EntityRowType[] = [...entities];
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from "react";
|
||||
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
||||
import * as Entities from "../../Tables/Entities";
|
||||
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import { EditTableEntityPanel } from "./EditTableEntityPanel";
|
||||
|
||||
describe("Excute Edit Table Entity Pane", () => {
|
||||
@@ -15,11 +15,14 @@ describe("Excute Edit Table Entity Pane", () => {
|
||||
fakeTableEntityListViewModel.headers = [];
|
||||
fakeTableEntityListViewModel.selected = ko.observableArray<Entities.ITableEntity>([{}]);
|
||||
|
||||
const fakeSelectedItem = [{ PartitionKey: { _: "test", $: "String" } }];
|
||||
const props = {
|
||||
tableDataClient: new TablesAPIDataClient(),
|
||||
queryTablesTab: fakeQueryTablesTab,
|
||||
tableEntityListViewModel: fakeTableEntityListViewModel,
|
||||
cassandraApiClient: fakeCassandraApiClient,
|
||||
selectedEntity: fakeSelectedItem,
|
||||
reloadEntities: () => "{}",
|
||||
};
|
||||
|
||||
it("should render Default properly", () => {
|
||||
@@ -29,13 +32,13 @@ describe("Excute Edit Table Entity Pane", () => {
|
||||
|
||||
it("initially display 4 input field, 2 properties and 1 entity values", () => {
|
||||
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(0);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("add a new entity row", () => {
|
||||
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||
wrapper.find(".addButtonEntiy").last().simulate("click");
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("remove a entity field", () => {
|
||||
@@ -43,6 +46,6 @@ describe("Excute Edit Table Entity Pane", () => {
|
||||
// Since default entity row doesn't have delete option, so added row then delete for test cases.
|
||||
wrapper.find(".addButtonEntiy").last().simulate("click");
|
||||
wrapper.find("#deleteEntity").last().simulate("click");
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(0);
|
||||
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,14 +6,14 @@ import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { TableEntity } from "../../../Common/TableEntity";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as TableConstants from "../../Tables/Constants";
|
||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||
import * as Entities from "../../Tables/Entities";
|
||||
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
|
||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
import {
|
||||
attributeNameLabel,
|
||||
@@ -34,9 +34,11 @@ import {
|
||||
|
||||
interface EditTableEntityPanelProps {
|
||||
tableDataClient: TableDataClient;
|
||||
queryTablesTab: QueryTablesTab;
|
||||
queryTablesTab: NewQueryTablesTab;
|
||||
tableEntityListViewModel: TableEntityListViewModel;
|
||||
cassandraApiClient: CassandraAPIDataClient;
|
||||
selectedEntity: Entities.ITableEntity[];
|
||||
reloadEntities: () => void;
|
||||
}
|
||||
|
||||
interface EntityRowType {
|
||||
@@ -57,7 +59,10 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
queryTablesTab,
|
||||
tableEntityListViewModel,
|
||||
cassandraApiClient,
|
||||
selectedEntity,
|
||||
reloadEntities,
|
||||
}: EditTableEntityPanelProps): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||
@@ -75,8 +80,8 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let originalDocument: { [key: string]: any } = {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const entityAttribute: any = tableEntityListViewModel.selected();
|
||||
const entityFormattedAttribute = constructDisplayedAttributes(entityAttribute[0]);
|
||||
const entityAttribute: any = selectedEntity;
|
||||
const entityFormattedAttribute = constructDisplayedAttributes(entityAttribute && entityAttribute[0]);
|
||||
setEntities(entityFormattedAttribute);
|
||||
|
||||
if (userContext.apiType === "Tables") {
|
||||
@@ -86,6 +91,7 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
originalDocument = entityAttribute;
|
||||
}
|
||||
setOriginalDocument(originalDocument);
|
||||
//eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const constructDisplayedAttributes = (entity: Entities.ITableEntity): EntityRowType[] => {
|
||||
@@ -216,9 +222,8 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
entity
|
||||
);
|
||||
await tableEntityListViewModel.updateCachedEntity(newEntity);
|
||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||
tableEntityListViewModel.redrawTableThrottled();
|
||||
}
|
||||
reloadEntities();
|
||||
closeSidePanel();
|
||||
tableEntityListViewModel.selected.removeAll();
|
||||
tableEntityListViewModel.selected.push(newEntity);
|
||||
} catch (error) {
|
||||
@@ -230,34 +235,6 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
}
|
||||
};
|
||||
|
||||
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
||||
let newHeaders: string[] = [];
|
||||
const keys = Object.keys(newEntity);
|
||||
keys &&
|
||||
keys.forEach((key: string) => {
|
||||
if (
|
||||
!_.contains(viewModel.headers, key) &&
|
||||
key !== TableEntityProcessor.keyProperties.attachments &&
|
||||
key !== TableEntityProcessor.keyProperties.etag &&
|
||||
key !== TableEntityProcessor.keyProperties.resourceId &&
|
||||
key !== TableEntityProcessor.keyProperties.self &&
|
||||
(!(userContext.apiType === "Cassandra") || key !== TableConstants.EntityKeyNames.RowKey)
|
||||
) {
|
||||
newHeaders.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
let newHeadersInserted = false;
|
||||
if (newHeaders.length) {
|
||||
if (!DataTableUtilities.checkForDefaultHeader(viewModel.headers)) {
|
||||
newHeaders = viewModel.headers.concat(newHeaders);
|
||||
}
|
||||
viewModel.updateHeaders(newHeaders, /* notifyColumnChanges */ true, /* enablePrompt */ false);
|
||||
newHeadersInserted = true;
|
||||
}
|
||||
return newHeadersInserted;
|
||||
};
|
||||
|
||||
// Add new entity row
|
||||
const addNewEntity = (): void => {
|
||||
const cloneEntities = [...entities];
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { mount } from "enzyme";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import Explorer from "../../../Explorer";
|
||||
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
||||
import { TableQuerySelectPanel } from "./TableQuerySelectPanel";
|
||||
|
||||
describe("Table query select Panel", () => {
|
||||
const fakeExplorer = {} as Explorer;
|
||||
const fakeQueryViewModal = {} as QueryViewModel;
|
||||
fakeQueryViewModal.columnOptions = ko.observableArray<string>([""]);
|
||||
|
||||
const props = {
|
||||
explorer: fakeExplorer,
|
||||
closePanel: (): void => undefined,
|
||||
headers: [""],
|
||||
getSelectMessage: () => "{}",
|
||||
queryViewModel: fakeQueryViewModal,
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { RightPaneForm, RightPaneFormProps } from "../../RightPaneForm/RightPane
|
||||
|
||||
interface TableQuerySelectPanelProps {
|
||||
queryViewModel: QueryViewModel;
|
||||
headers: string[];
|
||||
getSelectMessage: (selectMessage: string) => void;
|
||||
}
|
||||
|
||||
interface ISelectColumn {
|
||||
@@ -18,6 +20,8 @@ interface ISelectColumn {
|
||||
|
||||
export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps> = ({
|
||||
queryViewModel,
|
||||
headers,
|
||||
getSelectMessage,
|
||||
}: TableQuerySelectPanelProps): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
|
||||
@@ -29,6 +33,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
||||
const onSubmit = (): void => {
|
||||
queryViewModel.selectText(getParameters());
|
||||
queryViewModel.getSelectMessage();
|
||||
getSelectMessage(queryViewModel.selectMessage());
|
||||
closeSidePanel();
|
||||
};
|
||||
|
||||
@@ -52,7 +57,8 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryViewModel && setTableColumns(queryViewModel.columnOptions());
|
||||
// queryViewModel && setTableColumns(queryViewModel.columnOptions());
|
||||
headers && setTableColumns(headers);
|
||||
}, []);
|
||||
|
||||
const setTableColumns = (columnNames: string[]): void => {
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
exports[`Table query select Panel should render Default properly 1`] = `
|
||||
<TableQuerySelectPanel
|
||||
closePanel={[Function]}
|
||||
explorer={Object {}}
|
||||
getSelectMessage={[Function]}
|
||||
headers={
|
||||
Array [
|
||||
"",
|
||||
]
|
||||
}
|
||||
queryViewModel={
|
||||
Object {
|
||||
"columnOptions": [Function],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import { Upload } from "Common/Upload/Upload";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import { Upload } from "../../../Common/Upload/Upload";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
import { Upload } from "../../../Common/Upload/Upload";
|
||||
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { getErrorMessage } from "../../Tables/Utilities";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import * as _ from "underscore";
|
||||
|
||||
import * as Constants from "../Constants";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import * as DataTableBuilder from "./DataTableBuilder";
|
||||
import DataTableOperationManager from "./DataTableOperationManager";
|
||||
import * as DataTableOperations from "./DataTableOperations";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import TableEntityListViewModel from "./TableEntityListViewModel";
|
||||
import * as Utilities from "../Utilities";
|
||||
import * as Entities from "../Entities";
|
||||
|
||||
/**
|
||||
* Custom binding manager of datatable
|
||||
*/
|
||||
var tableEntityListViewModelMap: {
|
||||
[key: string]: {
|
||||
tableViewModel: TableEntityListViewModel;
|
||||
operationManager: DataTableOperationManager;
|
||||
$dataTable: JQuery;
|
||||
};
|
||||
} = {};
|
||||
|
||||
function bindDataTable(element: any, valueAccessor: any, allBindings: any, viewModel: any, bindingContext: any) {
|
||||
var tableEntityListViewModel = bindingContext.$data;
|
||||
tableEntityListViewModel.notifyColumnChanges = onTableColumnChange;
|
||||
var $dataTable = $(element);
|
||||
var queryTablesTab = bindingContext.$parent;
|
||||
var operationManager = new DataTableOperationManager(
|
||||
$dataTable,
|
||||
tableEntityListViewModel,
|
||||
queryTablesTab.tableCommands
|
||||
);
|
||||
|
||||
tableEntityListViewModelMap[queryTablesTab.tabId] = {
|
||||
tableViewModel: tableEntityListViewModel,
|
||||
operationManager: operationManager,
|
||||
$dataTable: $dataTable,
|
||||
};
|
||||
|
||||
createDataTable(0, tableEntityListViewModel, queryTablesTab); // Fake a DataTable to start.
|
||||
$(window).resize(updateTableScrollableRegionMetrics);
|
||||
operationManager.focusTable(); // Also selects the first row if needed.
|
||||
}
|
||||
|
||||
function onTableColumnChange(enablePrompt: boolean = true, queryTablesTab: QueryTablesTab) {
|
||||
var columnsFilter: boolean[] = null;
|
||||
var tableEntityListViewModel = tableEntityListViewModelMap[queryTablesTab.tabId].tableViewModel;
|
||||
if (queryTablesTab.queryViewModel()) {
|
||||
queryTablesTab.queryViewModel().queryBuilderViewModel().updateColumnOptions();
|
||||
}
|
||||
createDataTable(
|
||||
tableEntityListViewModel.tablePageStartIndex,
|
||||
tableEntityListViewModel,
|
||||
queryTablesTab,
|
||||
true,
|
||||
columnsFilter
|
||||
);
|
||||
}
|
||||
|
||||
function createDataTable(
|
||||
startIndex: number,
|
||||
tableEntityListViewModel: TableEntityListViewModel,
|
||||
queryTablesTab: QueryTablesTab,
|
||||
destroy: boolean = false,
|
||||
columnsFilter: boolean[] = null
|
||||
): void {
|
||||
var $dataTable = tableEntityListViewModelMap[queryTablesTab.tabId].$dataTable;
|
||||
if (destroy) {
|
||||
// Find currently displayed columns.
|
||||
var currentColumns: string[] = tableEntityListViewModel.headers;
|
||||
|
||||
// Calculate how many more columns need to added to the current table.
|
||||
var columnsToAdd: number = _.difference(tableEntityListViewModel.headers, currentColumns).length;
|
||||
|
||||
// This is needed as current solution of adding column is more like a workaround
|
||||
// The official support for dynamically add column is not yet there
|
||||
// Please track github issue https://github.com/DataTables/DataTables/issues/273 for its offical support
|
||||
for (var i = 0; i < columnsToAdd; i++) {
|
||||
$(".dataTables_scrollHead table thead tr th").eq(0).after("<th></th>");
|
||||
}
|
||||
tableEntityListViewModel.table.destroy();
|
||||
$dataTable.empty();
|
||||
}
|
||||
|
||||
var jsonColTable = [];
|
||||
|
||||
for (var i = 0; i < tableEntityListViewModel.headers.length; i++) {
|
||||
jsonColTable.push({
|
||||
sTitle: tableEntityListViewModel.headers[i],
|
||||
data: tableEntityListViewModel.headers[i],
|
||||
aTargets: [i],
|
||||
mRender: bindColumn,
|
||||
visible: !!columnsFilter ? columnsFilter[i] : true,
|
||||
});
|
||||
}
|
||||
|
||||
tableEntityListViewModel.table = DataTableBuilder.createDataTable($dataTable, <DataTables.Settings>{
|
||||
// WARNING!!! SECURITY: If you add new columns, make sure you encode them if they are user strings from Azure (see encodeText)
|
||||
// so that they don't get interpreted as HTML in our page.
|
||||
colReorder: true,
|
||||
aoColumnDefs: jsonColTable,
|
||||
stateSave: false,
|
||||
dom: "RZlfrtip",
|
||||
oColReorder: {
|
||||
iFixedColumns: 1,
|
||||
},
|
||||
displayStart: startIndex,
|
||||
bPaginate: true,
|
||||
pagingType: "full_numbers",
|
||||
bProcessing: true,
|
||||
oLanguage: {
|
||||
sInfo: "Results _START_ - _END_ of _TOTAL_",
|
||||
oPaginate: {
|
||||
sFirst: "<<",
|
||||
sNext: ">",
|
||||
sPrevious: "<",
|
||||
sLast: ">>",
|
||||
},
|
||||
sProcessing: '<img style="width: 28px; height: 6px; " src="images/LoadingIndicator_3Squares.gif">',
|
||||
oAria: {
|
||||
sSortAscending: "",
|
||||
sSortDescending: "",
|
||||
},
|
||||
},
|
||||
destroy: destroy,
|
||||
bInfo: true,
|
||||
bLength: false,
|
||||
bLengthChange: false,
|
||||
scrollX: true,
|
||||
scrollCollapse: true,
|
||||
iDisplayLength: 100,
|
||||
serverSide: true,
|
||||
ajax: queryTablesTab.tabId, // Using this settings to make sure for getServerData we update the table based on the appropriate tab
|
||||
fnServerData: getServerData,
|
||||
fnRowCallback: bindClientId,
|
||||
fnInitComplete: initializeTable,
|
||||
fnDrawCallback: updateSelectionStatus,
|
||||
});
|
||||
|
||||
(tableEntityListViewModel.table.table(0).container() as Element)
|
||||
.querySelectorAll(Constants.htmlSelectors.dataTableHeaderTableSelector)
|
||||
.forEach((table) => {
|
||||
table.setAttribute(
|
||||
"summary",
|
||||
`Header for sorting results for container ${tableEntityListViewModel.queryTablesTab.collection.id()}`
|
||||
);
|
||||
});
|
||||
|
||||
(tableEntityListViewModel.table.table(0).container() as Element)
|
||||
.querySelectorAll(Constants.htmlSelectors.dataTableBodyTableSelector)
|
||||
.forEach((table) => {
|
||||
table.setAttribute("summary", `Results for container ${tableEntityListViewModel.queryTablesTab.collection.id()}`);
|
||||
});
|
||||
}
|
||||
|
||||
function bindColumn(data: any, type: string, full: any) {
|
||||
var displayedValue: any = null;
|
||||
if (data) {
|
||||
displayedValue = data._;
|
||||
|
||||
// SECURITY: Make sure we don't allow cross-site scripting by interpreting the values as HTML
|
||||
displayedValue = Utilities.htmlEncode(displayedValue);
|
||||
|
||||
// Css' empty psuedo class can only tell the difference of whether a cell has values.
|
||||
// A cell has no values no matter it's empty or it has no such a property.
|
||||
// To distinguish between an empty cell and a non-existing property cell,
|
||||
// we add a whitespace to the empty cell so that css will treat it as a cell with values.
|
||||
if (displayedValue === "" && data.$ === Constants.TableType.String) {
|
||||
displayedValue = " ";
|
||||
}
|
||||
}
|
||||
return displayedValue;
|
||||
}
|
||||
|
||||
function getServerData(sSource: any, aoData: any, fnCallback: any, oSettings: any) {
|
||||
tableEntityListViewModelMap[oSettings.ajax].tableViewModel.renderNextPageAndupdateCache(
|
||||
sSource,
|
||||
aoData,
|
||||
fnCallback,
|
||||
oSettings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind table data information to row element so that we can track back to the table data
|
||||
* from UI elements.
|
||||
*/
|
||||
function bindClientId(nRow: Node, aData: Entities.ITableEntity) {
|
||||
$(nRow).attr(Constants.htmlAttributeNames.dataTableRowKeyAttr, aData.RowKey._);
|
||||
return nRow;
|
||||
}
|
||||
|
||||
function selectionChanged(element: any, valueAccessor: any, allBindings: any, viewModel: any, bindingContext: any) {
|
||||
$(".dataTable tr.selected").attr("tabindex", "-1").removeClass("selected");
|
||||
|
||||
const selected =
|
||||
bindingContext && bindingContext.$data && bindingContext.$data.selected && bindingContext.$data.selected();
|
||||
selected &&
|
||||
selected.forEach((b: Entities.ITableEntity) => {
|
||||
var sel = DataTableOperations.getRowSelector([
|
||||
{
|
||||
key: Constants.htmlAttributeNames.dataTableRowKeyAttr,
|
||||
value: b.RowKey && b.RowKey._ && b.RowKey._.toString(),
|
||||
},
|
||||
]);
|
||||
|
||||
$(sel).attr("tabindex", "0").focus().addClass("selected");
|
||||
});
|
||||
//selected = bindingContext.$data.selected();
|
||||
}
|
||||
|
||||
function dataChanged(element: any, valueAccessor: any, allBindings: any, viewModel: any, bindingContext: any) {
|
||||
// do nothing for now
|
||||
}
|
||||
|
||||
function initializeTable(): void {
|
||||
updateTableScrollableRegionMetrics();
|
||||
initializeEventHandlers();
|
||||
}
|
||||
|
||||
function updateTableScrollableRegionMetrics(): void {
|
||||
updateTableScrollableRegionHeight();
|
||||
updateTableScrollableRegionWidth();
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the table's scrollable region height. So the pagination control is always shown at the bottom of the page.
|
||||
*/
|
||||
function updateTableScrollableRegionHeight(): void {
|
||||
$(".tab-pane").each(function (index, tabElement) {
|
||||
if (!$(tabElement).hasClass("tableContainer")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add some padding to the table so it doesn't get too close to the container border.
|
||||
var dataTablePaddingBottom = 10;
|
||||
var bodyHeight = $(window).height();
|
||||
var dataTablesScrollBodyPosY = $(tabElement).find(Constants.htmlSelectors.dataTableScrollBodySelector).offset().top;
|
||||
var dataTablesInfoElem = $(tabElement).find(".dataTables_info");
|
||||
var dataTablesPaginateElem = $(tabElement).find(".dataTables_paginate");
|
||||
const notificationConsoleHeight = 32; /** Header height **/
|
||||
|
||||
var scrollHeight =
|
||||
bodyHeight -
|
||||
dataTablesScrollBodyPosY -
|
||||
dataTablesPaginateElem.outerHeight(true) -
|
||||
dataTablePaddingBottom -
|
||||
notificationConsoleHeight;
|
||||
|
||||
//info and paginate control are stacked
|
||||
if (dataTablesInfoElem.offset().top < dataTablesPaginateElem.offset().top) {
|
||||
scrollHeight -= dataTablesInfoElem.outerHeight(true);
|
||||
}
|
||||
|
||||
// TODO This is a work around for setting the outerheight since we don't have access to the JQuery.outerheight(numberValue)
|
||||
// in the current version of JQuery we are using. Ideally, we would upgrade JQuery and use this line instead:
|
||||
// $(Constants.htmlSelectors.dataTableScrollBodySelector).outerHeight(scrollHeight);
|
||||
var element = $(tabElement).find(Constants.htmlSelectors.dataTableScrollBodySelector)[0];
|
||||
var style = getComputedStyle(element);
|
||||
var actualHeight = parseInt(style.height);
|
||||
var change = element.offsetHeight - scrollHeight;
|
||||
$(tabElement)
|
||||
.find(Constants.htmlSelectors.dataTableScrollBodySelector)
|
||||
.height(actualHeight - change);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the table's scrollable region width to make efficient use of the remaining space.
|
||||
*/
|
||||
function updateTableScrollableRegionWidth(): void {
|
||||
$(".tab-pane").each(function (index, tabElement) {
|
||||
if (!$(tabElement).hasClass("tableContainer")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bodyWidth = $(window).width();
|
||||
var dataTablesScrollBodyPosLeft = $(tabElement).find(Constants.htmlSelectors.dataTableScrollBodySelector).offset()
|
||||
.left;
|
||||
var scrollWidth = bodyWidth - dataTablesScrollBodyPosLeft;
|
||||
|
||||
// jquery datatables automatically sets width:100% to both the header and the body when we use it's column autoWidth feature.
|
||||
// We work around that by setting the height for it's container instead.
|
||||
$(tabElement).find(Constants.htmlSelectors.dataTableScrollContainerSelector).width(scrollWidth);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeEventHandlers(): void {
|
||||
var $headers: JQuery = $(Constants.htmlSelectors.dataTableHeaderTypeSelector);
|
||||
var $firstHeader: JQuery = $headers.first();
|
||||
var firstIndex: string = $firstHeader.attr(Constants.htmlAttributeNames.dataTableHeaderIndex);
|
||||
|
||||
$headers
|
||||
.on("keydown", (event: JQueryEventObject) => {
|
||||
Utilities.onEnter(event, ($sourceElement: JQuery) => {
|
||||
$sourceElement.css("background-color", Constants.cssColors.commonControlsButtonActive);
|
||||
});
|
||||
|
||||
// Bind shift+tab from first header back to search input field
|
||||
Utilities.onTab(
|
||||
event,
|
||||
($sourceElement: JQuery) => {
|
||||
var sourceIndex: string = $sourceElement.attr(Constants.htmlAttributeNames.dataTableHeaderIndex);
|
||||
|
||||
if (sourceIndex === firstIndex) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
/* metaKey */ null,
|
||||
/* shiftKey */ true,
|
||||
/* altKey */ null
|
||||
);
|
||||
|
||||
// Also reset color if [shift-] tabbing away from button while holding down 'enter'
|
||||
Utilities.onTab(event, ($sourceElement: JQuery) => {
|
||||
$sourceElement.css("background-color", "");
|
||||
});
|
||||
})
|
||||
.on("keyup", (event: JQueryEventObject) => {
|
||||
Utilities.onEnter(event, ($sourceElement: JQuery) => {
|
||||
$sourceElement.css("background-color", "");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectionStatus(oSettings: any): void {
|
||||
var $dataTableRows: JQuery = $(Constants.htmlSelectors.dataTableAllRowsSelector);
|
||||
if ($dataTableRows) {
|
||||
for (var i = 0; i < $dataTableRows.length; i++) {
|
||||
var $row: JQuery = $dataTableRows.eq(i);
|
||||
var rowKey: string = $row.attr(Constants.htmlAttributeNames.dataTableRowKeyAttr);
|
||||
var table = tableEntityListViewModelMap[oSettings.ajax].tableViewModel;
|
||||
if (table.isItemSelected(table.getTableEntityKeys(rowKey))) {
|
||||
$row.attr("tabindex", "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDataTableFocus(oSettings.ajax);
|
||||
|
||||
DataTableOperations.setPaginationButtonEventHandlers();
|
||||
}
|
||||
|
||||
// TODO consider centralizing this "post-command" logic into some sort of Command Manager entity.
|
||||
// See VSO:166520: "[Storage Explorer] Consider adding a 'command manager' to track command post-effects."
|
||||
function updateDataTableFocus(queryTablesTabId: string): void {
|
||||
var $activeElement: JQuery = $(document.activeElement);
|
||||
var isFocusLost: boolean = $activeElement.is("body"); // When focus is lost, "body" becomes the active element.
|
||||
var storageExplorerFrameHasFocus: boolean = document.hasFocus();
|
||||
var operationManager = tableEntityListViewModelMap[queryTablesTabId].operationManager;
|
||||
if (operationManager) {
|
||||
if (isFocusLost && storageExplorerFrameHasFocus) {
|
||||
// We get here when no control is active, meaning that the table update was triggered
|
||||
// from a dialog, the context menu or by clicking on a toolbar control or header.
|
||||
// Note that giving focus to the table also selects the first row if needed.
|
||||
// The document.hasFocus() ensures that the table will only get focus when the
|
||||
// focus was lost (i.e. "body has the focus") within the Storage Explorer frame
|
||||
// i.e. not when the focus is lost because it is in another frame
|
||||
// e.g. a daytona dialog or in the Activity Log.
|
||||
operationManager.focusTable();
|
||||
}
|
||||
if ($activeElement.is(".sorting_asc") || $activeElement.is(".sorting_desc")) {
|
||||
// If table header is selected, focus is shifted to the selected element as part of accessibility
|
||||
$activeElement && $activeElement.focus();
|
||||
} else {
|
||||
// If some control is active, we don't give focus back to the table,
|
||||
// just select the first row if needed (empty selection).
|
||||
operationManager.selectFirstIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(<any>ko.bindingHandlers).tableSource = {
|
||||
init: bindDataTable,
|
||||
update: dataChanged,
|
||||
};
|
||||
|
||||
(<any>ko.bindingHandlers).tableSelection = {
|
||||
update: selectionChanged,
|
||||
};
|
||||
|
||||
(<any>ko.bindingHandlers).readOnly = {
|
||||
update: function (element: any, valueAccessor: any) {
|
||||
var value = ko.utils.unwrapObservable(valueAccessor());
|
||||
if (value) {
|
||||
element.setAttribute("readOnly", true);
|
||||
} else {
|
||||
element.removeAttribute("readOnly");
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import * as Utilities from "../Utilities";
|
||||
|
||||
/**
|
||||
* Wrapper function for creating data tables. Call this method, not the
|
||||
* data tables constructor when you want to create a data table. This
|
||||
* function makes sure that content without a render function is properly
|
||||
* encoded to prevent XSS.
|
||||
* @param{$dataTableElem} JQuery data table element
|
||||
* @param{$settings} Settings to use when creating the data table
|
||||
*/
|
||||
export function createDataTable($dataTableElem: JQuery, settings: any): DataTables.DataTable {
|
||||
return $dataTableElem.DataTable(applyDefaultRendering(settings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the settings for a data table and apply a simple HTML encode to any column
|
||||
* without a render function to prevent XSS.
|
||||
* @param{settings} The settings to check
|
||||
* @return The given settings with all columns having a rendering function
|
||||
*/
|
||||
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
||||
var tableColumns: DataTables.ColumnLegacy[] = null;
|
||||
|
||||
if (settings.aoColumns) {
|
||||
tableColumns = settings.aoColumns;
|
||||
} else if (settings.aoColumnDefs) {
|
||||
// for tables we use aoColumnDefs instead of aoColumns
|
||||
tableColumns = settings.aoColumnDefs;
|
||||
}
|
||||
|
||||
// either the settings had no columns defined, or they were called
|
||||
// by a property name which we have not used before
|
||||
if (!tableColumns) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
for (var i = 0; i < tableColumns.length; i++) {
|
||||
// the column does not have a render function
|
||||
if (!tableColumns[i].mRender) {
|
||||
tableColumns[i].mRender = defaultDataRender;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default data render function, whatever is done to data in here
|
||||
* will be done to any data which we do not specify a render for.
|
||||
*/
|
||||
function defaultDataRender(data: any, type: string, full: any) {
|
||||
return Utilities.htmlEncode(data);
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
import ko from "knockout";
|
||||
|
||||
import * as DataTableOperations from "./DataTableOperations";
|
||||
import * as Constants from "../Constants";
|
||||
import TableCommands from "./TableCommands";
|
||||
import TableEntityListViewModel from "./TableEntityListViewModel";
|
||||
import * as Utilities from "../Utilities";
|
||||
import * as Entities from "../Entities";
|
||||
|
||||
/*
|
||||
* Base class for data table row selection.
|
||||
*/
|
||||
export default class DataTableOperationManager {
|
||||
private _tableEntityListViewModel: TableEntityListViewModel;
|
||||
private _tableCommands: TableCommands;
|
||||
private dataTable: JQuery;
|
||||
|
||||
constructor(table: JQuery, viewModel: TableEntityListViewModel, tableCommands: TableCommands) {
|
||||
this.dataTable = table;
|
||||
this._tableEntityListViewModel = viewModel;
|
||||
this._tableCommands = tableCommands;
|
||||
|
||||
this.bind();
|
||||
this._tableEntityListViewModel.bind(this);
|
||||
}
|
||||
|
||||
private click = (event: JQueryEventObject) => {
|
||||
var elem: JQuery = $(event.currentTarget);
|
||||
this.updateLastSelectedItem(elem, event.shiftKey);
|
||||
|
||||
if (Utilities.isEnvironmentCtrlPressed(event)) {
|
||||
this.applyCtrlSelection(elem);
|
||||
} else if (event.shiftKey) {
|
||||
this.applyShiftSelection(elem);
|
||||
} else {
|
||||
this.applySingleSelection(elem);
|
||||
}
|
||||
};
|
||||
|
||||
private doubleClick = (event: JQueryEventObject) => {
|
||||
this.tryOpenEditor();
|
||||
};
|
||||
|
||||
private keyDown = (event: JQueryEventObject): boolean => {
|
||||
var isUpArrowKey: boolean = event.keyCode === Constants.keyCodes.UpArrow,
|
||||
isDownArrowKey: boolean = event.keyCode === Constants.keyCodes.DownArrow,
|
||||
handled: boolean = false;
|
||||
|
||||
if (isUpArrowKey || isDownArrowKey) {
|
||||
var lastSelectedItem: Entities.ITableEntity = this._tableEntityListViewModel.lastSelectedItem;
|
||||
var dataTableRows: JQuery = $(Constants.htmlSelectors.dataTableAllRowsSelector);
|
||||
var maximumIndex = dataTableRows.length - 1;
|
||||
|
||||
// If can't find an index for lastSelectedItem, then either no item is previously selected or it goes across page.
|
||||
// Simply select the first item in this case.
|
||||
var lastSelectedItemIndex = lastSelectedItem
|
||||
? this._tableEntityListViewModel.getItemIndexFromCurrentPage(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(lastSelectedItem.RowKey._)
|
||||
)
|
||||
: -1;
|
||||
var nextIndex: number = isUpArrowKey ? lastSelectedItemIndex - 1 : lastSelectedItemIndex + 1;
|
||||
var safeIndex: number = Utilities.ensureBetweenBounds(nextIndex, 0, maximumIndex);
|
||||
var selectedRowElement: JQuery = dataTableRows.eq(safeIndex);
|
||||
|
||||
if (selectedRowElement) {
|
||||
if (event.shiftKey) {
|
||||
this.applyShiftSelection(selectedRowElement);
|
||||
} else {
|
||||
this.applySingleSelection(selectedRowElement);
|
||||
}
|
||||
|
||||
this.updateLastSelectedItem(selectedRowElement, event.shiftKey);
|
||||
handled = true;
|
||||
DataTableOperations.scrollToRowIfNeeded(dataTableRows, safeIndex, isUpArrowKey);
|
||||
}
|
||||
} else if (
|
||||
Utilities.isEnvironmentCtrlPressed(event) &&
|
||||
!Utilities.isEnvironmentShiftPressed(event) &&
|
||||
!Utilities.isEnvironmentAltPressed(event) &&
|
||||
event.keyCode === Constants.keyCodes.A
|
||||
) {
|
||||
this.applySelectAll();
|
||||
handled = true;
|
||||
}
|
||||
|
||||
return !handled;
|
||||
};
|
||||
|
||||
// Note: There is one key up event each time a key is pressed;
|
||||
// in contrast, there may be more than one key down and key
|
||||
// pressed events.
|
||||
private keyUp = (event: JQueryEventObject): boolean => {
|
||||
var handled: boolean = false;
|
||||
|
||||
switch (event.keyCode) {
|
||||
case Constants.keyCodes.Enter:
|
||||
handled = this.tryOpenEditor();
|
||||
break;
|
||||
case Constants.keyCodes.Delete:
|
||||
handled = this.tryHandleDeleteSelected();
|
||||
break;
|
||||
}
|
||||
|
||||
return !handled;
|
||||
};
|
||||
|
||||
private itemDropped = (event: JQueryEventObject): boolean => {
|
||||
var handled: boolean = false;
|
||||
var items = (<any>event.originalEvent).dataTransfer.items;
|
||||
|
||||
if (!items) {
|
||||
// On browsers outside of Chromium
|
||||
// we can't discern between dirs and files
|
||||
// so we will disable drag & drop for now
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
var entry = item.webkitGetAsEntry();
|
||||
|
||||
if (entry.isFile) {
|
||||
// TODO: parse the file and insert content as entities
|
||||
}
|
||||
}
|
||||
|
||||
return !handled;
|
||||
};
|
||||
|
||||
private tryOpenEditor(): boolean {
|
||||
return this._tableCommands.tryOpenEntityEditor(this._tableEntityListViewModel);
|
||||
}
|
||||
|
||||
private tryHandleDeleteSelected(): boolean {
|
||||
var selectedEntities: Entities.ITableEntity[] = this._tableEntityListViewModel.selected();
|
||||
var handled: boolean = false;
|
||||
|
||||
if (selectedEntities && selectedEntities.length) {
|
||||
this._tableCommands.deleteEntitiesCommand(this._tableEntityListViewModel);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
private getEntityIdentity($elem: JQuery): Entities.ITableEntityIdentity {
|
||||
return {
|
||||
RowKey: $elem.attr(Constants.htmlAttributeNames.dataTableRowKeyAttr),
|
||||
};
|
||||
}
|
||||
|
||||
private updateLastSelectedItem($elem: JQuery, isShiftSelect: boolean) {
|
||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||
var entity = this._tableEntityListViewModel.getItemFromCurrentPage(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(entityIdentity.RowKey)
|
||||
);
|
||||
|
||||
this._tableEntityListViewModel.lastSelectedItem = entity;
|
||||
|
||||
if (!isShiftSelect) {
|
||||
this._tableEntityListViewModel.lastSelectedAnchorItem = entity;
|
||||
}
|
||||
}
|
||||
|
||||
private applySingleSelection($elem: JQuery) {
|
||||
if ($elem) {
|
||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||
|
||||
this._tableEntityListViewModel.clearSelection();
|
||||
this.addToSelection(entityIdentity.RowKey);
|
||||
}
|
||||
}
|
||||
|
||||
private applySelectAll() {
|
||||
this._tableEntityListViewModel.clearSelection();
|
||||
ko.utils.arrayPushAll<Entities.ITableEntity>(
|
||||
this._tableEntityListViewModel.selected,
|
||||
this._tableEntityListViewModel.getAllItemsInCurrentPage()
|
||||
);
|
||||
}
|
||||
|
||||
private applyCtrlSelection($elem: JQuery): void {
|
||||
var koSelected: ko.ObservableArray<Entities.ITableEntity> = this._tableEntityListViewModel
|
||||
? this._tableEntityListViewModel.selected
|
||||
: null;
|
||||
|
||||
if (koSelected) {
|
||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||
|
||||
if (
|
||||
!this._tableEntityListViewModel.isItemSelected(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(entityIdentity.RowKey)
|
||||
)
|
||||
) {
|
||||
// Adding item not previously in selection
|
||||
this.addToSelection(entityIdentity.RowKey);
|
||||
} else {
|
||||
koSelected.remove((item: Entities.ITableEntity) => item.RowKey._ === entityIdentity.RowKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private applyShiftSelection($elem: JQuery): void {
|
||||
var anchorItem = this._tableEntityListViewModel.lastSelectedAnchorItem;
|
||||
|
||||
// If anchor item doesn't exist, use the first available item of current page instead
|
||||
if (!anchorItem && this._tableEntityListViewModel.items().length > 0) {
|
||||
anchorItem = this._tableEntityListViewModel.items()[0];
|
||||
}
|
||||
|
||||
if (anchorItem) {
|
||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||
var elementIndex = this._tableEntityListViewModel.getItemIndexFromAllPages(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(entityIdentity.RowKey)
|
||||
);
|
||||
var anchorIndex = this._tableEntityListViewModel.getItemIndexFromAllPages(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(anchorItem.RowKey._)
|
||||
);
|
||||
|
||||
var startIndex = Math.min(elementIndex, anchorIndex);
|
||||
var endIndex = Math.max(elementIndex, anchorIndex);
|
||||
|
||||
this._tableEntityListViewModel.clearSelection();
|
||||
ko.utils.arrayPushAll<Entities.ITableEntity>(
|
||||
this._tableEntityListViewModel.selected,
|
||||
this._tableEntityListViewModel.getItemsFromAllPagesWithinRange(startIndex, endIndex + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private applyContextMenuSelection($elem: JQuery) {
|
||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||
|
||||
if (
|
||||
!this._tableEntityListViewModel.isItemSelected(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(entityIdentity.RowKey)
|
||||
)
|
||||
) {
|
||||
if (this._tableEntityListViewModel.selected().length) {
|
||||
this._tableEntityListViewModel.clearSelection();
|
||||
}
|
||||
this.addToSelection(entityIdentity.RowKey);
|
||||
}
|
||||
}
|
||||
|
||||
private addToSelection(rowKey: string) {
|
||||
var selectedEntity: Entities.ITableEntity = this._tableEntityListViewModel.getItemFromCurrentPage(
|
||||
this._tableEntityListViewModel.getTableEntityKeys(rowKey)
|
||||
);
|
||||
|
||||
if (selectedEntity != null) {
|
||||
this._tableEntityListViewModel.selected.push(selectedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
// Selecting first row if the selection is empty.
|
||||
public selectFirstIfNeeded(): void {
|
||||
var koSelected: ko.ObservableArray<Entities.ITableEntity> = this._tableEntityListViewModel
|
||||
? this._tableEntityListViewModel.selected
|
||||
: null;
|
||||
var koEntities: ko.ObservableArray<Entities.ITableEntity> = this._tableEntityListViewModel
|
||||
? this._tableEntityListViewModel.items
|
||||
: null;
|
||||
|
||||
if (!koSelected().length && koEntities().length) {
|
||||
var firstEntity: Entities.ITableEntity = koEntities()[0];
|
||||
|
||||
// Clear last selection: lastSelectedItem and lastSelectedAnchorItem
|
||||
this._tableEntityListViewModel.clearLastSelected();
|
||||
|
||||
this.addToSelection(firstEntity.RowKey._);
|
||||
|
||||
// Update last selection
|
||||
this._tableEntityListViewModel.lastSelectedItem = firstEntity;
|
||||
|
||||
// Finally, make sure first row is visible
|
||||
DataTableOperations.scrollToTopIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public bind() {
|
||||
this.dataTable.on("click", "tr", this.click);
|
||||
this.dataTable.on("dblclick", "tr", this.doubleClick);
|
||||
this.dataTable.on("keydown", "td", this.keyDown);
|
||||
this.dataTable.on("keyup", "td", this.keyUp);
|
||||
|
||||
// Keyboard navigation - selecting first row if the selection is empty when the table gains focus.
|
||||
this.dataTable.on("focus", () => {
|
||||
this.selectFirstIfNeeded();
|
||||
return true;
|
||||
});
|
||||
|
||||
// Bind drag & drop behavior
|
||||
$("body").on("drop", this.itemDropped);
|
||||
}
|
||||
|
||||
public focusTable(): void {
|
||||
this.dataTable.focus();
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import Q from "q";
|
||||
import _ from "underscore";
|
||||
import * as QueryBuilderConstants from "../Constants";
|
||||
import * as Entities from "../Entities";
|
||||
import * as Utilities from "../Utilities";
|
||||
|
||||
export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
|
||||
let selector = "";
|
||||
selectorSchema &&
|
||||
selectorSchema.forEach((p: Entities.IProperty) => {
|
||||
selector += "[" + p.key + '="' + Utilities.jQuerySelectorEscape(p.value) + '"]';
|
||||
});
|
||||
return QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector + selector;
|
||||
}
|
||||
|
||||
export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElement): boolean {
|
||||
let isVisible = false;
|
||||
|
||||
if (dataTableScrollBodyQuery.length && element) {
|
||||
const elementRect: ClientRect = element.getBoundingClientRect(),
|
||||
dataTableScrollBodyRect: ClientRect = dataTableScrollBodyQuery.get(0).getBoundingClientRect();
|
||||
|
||||
isVisible = elementRect.bottom <= dataTableScrollBodyRect.bottom && dataTableScrollBodyRect.top <= elementRect.top;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number, isScrollUp: boolean): void {
|
||||
if (dataTableRows.length) {
|
||||
const dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector),
|
||||
selectedRowElement: HTMLElement = dataTableRows.get(currentIndex);
|
||||
|
||||
if (dataTableScrollBodyQuery.length && selectedRowElement) {
|
||||
const isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement);
|
||||
|
||||
if (!isVisible) {
|
||||
const selectedRowQuery: JQuery = $(selectedRowElement),
|
||||
scrollPosition: number = dataTableScrollBodyQuery.scrollTop(),
|
||||
selectedElementPosition: number = selectedRowQuery.position().top;
|
||||
let newScrollPosition = 0;
|
||||
|
||||
if (isScrollUp) {
|
||||
newScrollPosition = scrollPosition + selectedElementPosition;
|
||||
} else {
|
||||
newScrollPosition =
|
||||
scrollPosition + (selectedElementPosition + selectedRowQuery.height() - dataTableScrollBodyQuery.height());
|
||||
}
|
||||
|
||||
dataTableScrollBodyQuery.scrollTop(newScrollPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function scrollToTopIfNeeded(): void {
|
||||
const $dataTableRows: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector),
|
||||
$dataTableScrollBody: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector);
|
||||
|
||||
if ($dataTableRows.length && $dataTableScrollBody.length) {
|
||||
$dataTableScrollBody.scrollTop(0);
|
||||
}
|
||||
}
|
||||
|
||||
export function setPaginationButtonEventHandlers(): void {
|
||||
$(QueryBuilderConstants.htmlSelectors.dataTablePaginationButtonSelector)
|
||||
.on("mousedown", (event: JQueryEventObject) => {
|
||||
// Prevents the table contents from briefly jumping when clicking on "Load more"
|
||||
event.preventDefault();
|
||||
})
|
||||
.attr("role", "button");
|
||||
}
|
||||
|
||||
export function filterColumns(table: DataTables.DataTable, settings: boolean[]): void {
|
||||
settings &&
|
||||
settings.forEach((value: boolean, index: number) => {
|
||||
table.column(index).visible(value, false);
|
||||
});
|
||||
table.columns.adjust().draw(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder columns based on current order.
|
||||
* If no current order is specified, reorder the columns based on intial order.
|
||||
*/
|
||||
export function reorderColumns(
|
||||
table: DataTables.DataTable,
|
||||
targetOrder: number[],
|
||||
currentOrder?: number[]
|
||||
//eslint-disable-next-line
|
||||
): Q.Promise<any> {
|
||||
const columnsCount: number = targetOrder.length;
|
||||
const isCurrentOrderPassedIn = !!currentOrder;
|
||||
if (!isCurrentOrderPassedIn) {
|
||||
currentOrder = getInitialOrder(columnsCount);
|
||||
}
|
||||
const isSameOrder: boolean = Utilities.isEqual(currentOrder, targetOrder);
|
||||
|
||||
// if the targetOrder is the same as current order, do nothing.
|
||||
if (!isSameOrder) {
|
||||
// Otherwise, calculate the transformation order.
|
||||
// If current order not specified, then it'll be set to initial order,
|
||||
// i.e., either no reorder happened before or reordering to its initial order,
|
||||
// Then the transformation order will be the same as target order.
|
||||
// If current order is specified, then a transformation order is calculated.
|
||||
// Refer to calculateTransformationOrder for details about transformation order.
|
||||
const transformationOrder: number[] = isCurrentOrderPassedIn
|
||||
? calculateTransformationOrder(currentOrder, targetOrder)
|
||||
: targetOrder;
|
||||
try {
|
||||
$.fn.dataTable.ColReorder(table).fnOrder(transformationOrder);
|
||||
} catch (err) {
|
||||
return Q.reject(err);
|
||||
}
|
||||
}
|
||||
return Q.resolve(null);
|
||||
}
|
||||
|
||||
export function resetColumns(table: DataTables.DataTable): void {
|
||||
$.fn.dataTable.ColReorder(table).fnReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* A table's initial order is described in the form of a natural ascending order.
|
||||
* E.g., for a table with 9 columns, the initial order will be: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
*/
|
||||
export function getInitialOrder(columnsCount: number): number[] {
|
||||
return _.range(columnsCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current table's column order which is described based on initial table. E.g.,
|
||||
* Initial order: I = [0, 1, 2, 3, 4, 5, 6, 7, 8] <----> {prop0, prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8}
|
||||
* Current order: C = [0, 1, 2, 6, 7, 3, 4, 5, 8] <----> {prop0, prop1, prop2, prop6, prop7, prop3, prop4, prop5, prop8}
|
||||
*/
|
||||
export function getCurrentOrder(table: DataTables.DataTable): number[] {
|
||||
return $.fn.dataTable.ColReorder(table).fnOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the index and value for each element of an array. e.g.,
|
||||
* InputArray: [0, 1, 2, 6, 7, 3, 4, 5, 8]
|
||||
* Result: [0, 1, 2, 5, 6, 7, 3, 4, 8]
|
||||
*/
|
||||
export function invertIndexValues(inputArray: number[]): number[] {
|
||||
const invertedArray: number[] = [];
|
||||
if (inputArray) {
|
||||
inputArray.forEach((value: number, index: number) => {
|
||||
invertedArray[inputArray[index]] = index;
|
||||
});
|
||||
}
|
||||
|
||||
return invertedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* DataTable fnOrder API is based on the current table. So we need to map the order targeting original table to targeting current table.
|
||||
* An detailed example for this. Assume the table has 9 columns.
|
||||
* Initial order (order of the initial table): I = [0, 1, 2, 3, 4, 5, 6, 7, 8] <----> {prop0, prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8}
|
||||
* Current order (order of the current table): C = [0, 1, 2, 6, 7, 3, 4, 5, 8] <----> {prop0, prop1, prop2, prop6, prop7, prop3, prop4, prop5, prop8}
|
||||
* Target order (order of the targeting table): T = [0, 1, 2, 5, 6, 7, 8, 3, 4] <----> {prop0, prop1, prop2, prop5, prop6, prop7, prop8, prop3, prop4}
|
||||
* Transformation order: an order passed to fnOrder API that transforms table from current order to target order.
|
||||
* When the table is constructed, it has the intial order. After an reordering with current order array, now the table is shown in current order, e.g.,
|
||||
* column 3 in the current table is actually column C[3]=6 in the intial table, both indicate the column with header prop6.
|
||||
* Now we want to continue to do another reorder to make the target table in the target order. Directly invoking API with the new order won't work as
|
||||
* the API only do reorder based on the current table like the first time we invoke the API. So an order based on the current table needs to be calulated.
|
||||
* Here is an example of how to calculate the transformation order:
|
||||
* In target table, column 3 should be column T[3]=5 in the intial table with header prop5, while in current table, column with header prop5 is column 7 as C[7]=5.
|
||||
* As a result, in transformation order, column 3 in the target table should be column 7 in the current table, Trans[3] = 7. In the same manner, we can get the
|
||||
* transformation order: Trans = [0, 1, 2, 7, 3, 4, 8, 5, 6]
|
||||
*/
|
||||
export function calculateTransformationOrder(currentOrder: number[], targetOrder: number[]): number[] {
|
||||
let transformationOrder: number[] = [];
|
||||
if (currentOrder && targetOrder && currentOrder.length === targetOrder.length) {
|
||||
const invertedCurrentOrder: number[] = invertIndexValues(currentOrder);
|
||||
transformationOrder = targetOrder.map((value: number) => invertedCurrentOrder[value]);
|
||||
}
|
||||
return transformationOrder;
|
||||
}
|
||||
|
||||
export function getDataTableHeaders(table: DataTables.DataTable): string[] {
|
||||
const columns: DataTables.ColumnsMethods = table.columns();
|
||||
let headers: string[] = [];
|
||||
if (columns) {
|
||||
// table.columns() return ColumnsMethods which is an array of arrays
|
||||
//eslint-disable-next-line
|
||||
const columnIndexes: number[] = (<any>columns)[0];
|
||||
if (columnIndexes) {
|
||||
headers = columnIndexes.map((value: number) => $(table.columns(value).header()).html());
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
@@ -123,10 +123,3 @@ export function checkForDefaultHeader(headers: string[]): boolean {
|
||||
export function forceRecalculateTableSize(): void {
|
||||
$("body").trigger("resize");
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off the spinning progress indicator on the data table.
|
||||
*/
|
||||
export function turnOffProgressIndicator(): void {
|
||||
$("div.dataTables_processing").hide();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as _ from "underscore";
|
||||
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import CacheBase from "./CacheBase";
|
||||
import * as CommonConstants from "../../../Common/Constants";
|
||||
import * as Constants from "../Constants";
|
||||
import * as Entities from "../Entities";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import * as Entities from "../Entities";
|
||||
import CacheBase from "./CacheBase";
|
||||
|
||||
// This is the format of the data we will have to pass to Datatable render callback,
|
||||
// and property names are defined by Datatable as well.
|
||||
@@ -47,19 +45,11 @@ abstract class DataTableViewModel {
|
||||
private pendingRedraw = false;
|
||||
private lastRedrawTime = new Date().getTime();
|
||||
|
||||
private dataTableOperationManager: IDataTableOperation;
|
||||
|
||||
public queryTablesTab: QueryTablesTab;
|
||||
public queryTablesTab: NewQueryTablesTab;
|
||||
|
||||
constructor() {
|
||||
this.items([]);
|
||||
this.selected([]);
|
||||
// Late bound
|
||||
this.dataTableOperationManager = null;
|
||||
}
|
||||
|
||||
public bind(dataTableOperationManager: IDataTableOperation): void {
|
||||
this.dataTableOperationManager = dataTableOperationManager;
|
||||
}
|
||||
|
||||
public clearLastSelected(): void {
|
||||
@@ -101,10 +91,6 @@ abstract class DataTableViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
public focusDataTable(): void {
|
||||
this.dataTableOperationManager.focusTable();
|
||||
}
|
||||
|
||||
public getItemFromSelectedItems(itemKeys: Entities.IProperty[]): Entities.ITableEntity {
|
||||
return _.find(this.selected(), (item: Entities.ITableEntity) => {
|
||||
return this.matchesKeys(item, itemKeys);
|
||||
@@ -170,35 +156,12 @@ abstract class DataTableViewModel {
|
||||
this.cache.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
protected renderPage(
|
||||
renderCallBack: any,
|
||||
draw: number,
|
||||
startIndex: number,
|
||||
pageSize: number,
|
||||
oSettings: any,
|
||||
postRenderTasks: (startIndex: number, pageSize: number) => Promise<void> = null
|
||||
) {
|
||||
this.updatePaginationControls(oSettings);
|
||||
|
||||
// pageSize < 0 means to show all data
|
||||
protected renderPage(startIndex: number, pageSize: number) {
|
||||
var endIndex = pageSize < 0 ? this.cache.length : startIndex + pageSize;
|
||||
var renderData = this.cache.data.slice(startIndex, endIndex);
|
||||
|
||||
this.items(renderData);
|
||||
|
||||
var render: IDataTableRenderData = {
|
||||
draw: draw,
|
||||
aaData: renderData,
|
||||
recordsTotal: this.cache.length,
|
||||
recordsFiltered: this.cache.length,
|
||||
};
|
||||
|
||||
if (!!postRenderTasks) {
|
||||
postRenderTasks(startIndex, pageSize).then(() => {
|
||||
this.table.rows().invalidate();
|
||||
});
|
||||
}
|
||||
renderCallBack(render);
|
||||
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.Tab,
|
||||
@@ -217,16 +180,6 @@ abstract class DataTableViewModel {
|
||||
protected matchesKeys(item: Entities.ITableEntity, itemKeys: Entities.IProperty[]): boolean {
|
||||
return itemKeys.every((property: Entities.IProperty) => {
|
||||
var itemValue = item[property.key];
|
||||
|
||||
// if (itemValue && property.subkey) {
|
||||
// itemValue = itemValue._[property.subkey];
|
||||
// if (!itemValue) {
|
||||
// itemValue = "";
|
||||
// }
|
||||
// } else if (property.subkey) {
|
||||
// itemValue = "";
|
||||
// }
|
||||
|
||||
return this.stringCompare(itemValue._, property.value);
|
||||
});
|
||||
}
|
||||
@@ -238,27 +191,6 @@ abstract class DataTableViewModel {
|
||||
protected stringCompare(s1: string, s2: string): boolean {
|
||||
return s1 === s2;
|
||||
}
|
||||
|
||||
private updatePaginationControls(oSettings: any) {
|
||||
var pageInfo = this.table.page.info();
|
||||
var pageSize = pageInfo.length;
|
||||
var paginateElement = $(oSettings.nTableWrapper).find(Constants.htmlSelectors.paginateSelector);
|
||||
|
||||
if (this.allDownloaded) {
|
||||
if (this.cache.length <= pageSize) {
|
||||
// Hide pagination controls if everything fits in one page!.
|
||||
paginateElement.hide();
|
||||
} else {
|
||||
// Enable pagination controls.
|
||||
paginateElement.show();
|
||||
oSettings.oLanguage.oPaginate.sLast = DataTableViewModel.lastPageLabel;
|
||||
}
|
||||
} else {
|
||||
// Enable pagination controls and show load more button.
|
||||
paginateElement.show();
|
||||
oSettings.oLanguage.oPaginate.sLast = DataTableViewModel.loadMoreLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IDataTableOperation {
|
||||
|
||||
@@ -8,11 +8,11 @@ import TableEntityListViewModel from "./TableEntityListViewModel";
|
||||
|
||||
export default class TableCommands {
|
||||
// Command Ids
|
||||
public static editEntityCommand = "edit";
|
||||
public static deleteEntitiesCommand = "delete";
|
||||
public static reorderColumnsCommand = "reorder";
|
||||
public static resetColumnsCommand = "reset";
|
||||
public static customizeColumnsCommand = "customizeColumns";
|
||||
public static editEntityCommand: string = "edit";
|
||||
public static deleteEntitiesCommand: string = "delete";
|
||||
public static reorderColumnsCommand: string = "reorder";
|
||||
public static resetColumnsCommand: string = "reset";
|
||||
public static customizeColumnsCommand: string = "customizeColumns";
|
||||
|
||||
private _container: Explorer;
|
||||
|
||||
@@ -21,8 +21,8 @@ export default class TableCommands {
|
||||
}
|
||||
|
||||
public isEnabled(commandName: string, selectedEntites: Entities.ITableEntity[]): boolean {
|
||||
const singleItemSelected = DataTableUtilities.containSingleItem(selectedEntites);
|
||||
const atLeastOneItemSelected = DataTableUtilities.containItems(selectedEntites);
|
||||
var singleItemSelected: boolean = DataTableUtilities.containSingleItem(selectedEntites);
|
||||
var atLeastOneItemSelected: boolean = DataTableUtilities.containItems(selectedEntites);
|
||||
switch (commandName) {
|
||||
case TableCommands.editEntityCommand:
|
||||
return singleItemSelected;
|
||||
@@ -47,7 +47,6 @@ export default class TableCommands {
|
||||
/**
|
||||
* Edit entity
|
||||
*/
|
||||
//eslint-disable-next-line
|
||||
public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
|
||||
if (!viewModel) {
|
||||
return null; // Error
|
||||
@@ -57,9 +56,12 @@ export default class TableCommands {
|
||||
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;
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
|
||||
public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
|
||||
if (!viewModel) {
|
||||
return null; // Error
|
||||
@@ -67,7 +69,7 @@ export default class TableCommands {
|
||||
if (!DataTableUtilities.containItems(viewModel.selected())) {
|
||||
return null; // Error
|
||||
}
|
||||
const entitiesToDelete: Entities.ITableEntity[] = viewModel.selected();
|
||||
var entitiesToDelete: Entities.ITableEntity[] = viewModel.selected();
|
||||
const deleteMessage: string =
|
||||
userContext.apiType === "Cassandra"
|
||||
? "Are you sure you want to delete the selected rows?"
|
||||
@@ -80,7 +82,7 @@ export default class TableCommands {
|
||||
() => {
|
||||
viewModel.queryTablesTab.container.tableDataClient
|
||||
.deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete)
|
||||
.then(() => {
|
||||
.then((results: any) => {
|
||||
return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => {
|
||||
viewModel.redrawTableThrottled();
|
||||
});
|
||||
@@ -92,8 +94,4 @@ export default class TableCommands {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public resetColumns(viewModel: TableEntityListViewModel): void {
|
||||
viewModel.reloadTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Entities from "../Entities";
|
||||
import * as Utilities from "../Utilities";
|
||||
import * as Entities from "../Entities";
|
||||
import CacheBase from "./CacheBase";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public preClear(): void {
|
||||
public preClear() {
|
||||
this.tableQuery = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import * as Constants from "../Constants";
|
||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||
import * as Entities from "../Entities";
|
||||
@@ -101,7 +101,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
public useSetting: boolean = true;
|
||||
|
||||
//public tableExplorerContext: TableExplorerContext;
|
||||
public notifyColumnChanges: (enablePrompt: boolean, queryTablesTab: QueryTablesTab) => void;
|
||||
public notifyColumnChanges: (enablePrompt: boolean, queryTablesTab: NewQueryTablesTab) => void;
|
||||
|
||||
public tablePageStartIndex: number;
|
||||
public tableQuery: Entities.ITableQuery = {};
|
||||
public cqlQuery: ko.Observable<string>;
|
||||
@@ -112,7 +113,7 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
public queryErrorMessage: ko.Observable<string>;
|
||||
public id: string;
|
||||
|
||||
constructor(tableCommands: TableCommands, queryTablesTab: QueryTablesTab) {
|
||||
constructor(tableCommands: TableCommands, queryTablesTab: NewQueryTablesTab) {
|
||||
super();
|
||||
this.cache = new TableEntityCache();
|
||||
this.queryErrorMessage = ko.observable<string>();
|
||||
@@ -131,24 +132,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
return [{ key: Constants.EntityKeyNames.RowKey, value: rowKey }];
|
||||
}
|
||||
|
||||
public reloadTable(useSetting: boolean = true, resetHeaders: boolean = true): DataTables.DataTable {
|
||||
this.clearCache();
|
||||
this.clearSelection();
|
||||
this.isCancelled = false;
|
||||
|
||||
this.useSetting = useSetting;
|
||||
if (resetHeaders) {
|
||||
this.updateHeaders([Constants.defaultHeader]);
|
||||
}
|
||||
return this.table.ajax.reload();
|
||||
}
|
||||
|
||||
public updateHeaders(newHeaders: string[], notifyColumnChanges: boolean = false, enablePrompt: boolean = true): void {
|
||||
this.headers = newHeaders;
|
||||
if (notifyColumnChanges) {
|
||||
this.clearSelection();
|
||||
this.notifyColumnChanges(enablePrompt, this.queryTablesTab);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,40 +143,21 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
* fnCallback - is the render callback with data to render.
|
||||
* oSetting: current settings used for table initialization.
|
||||
*/
|
||||
public renderNextPageAndupdateCache(sSource: any, aoData: any, fnCallback: any, oSettings: any) {
|
||||
|
||||
public async renderNextPageAndupdateCache(): Promise<Entities.ITableEntity[]> {
|
||||
var tablePageSize: number;
|
||||
var draw: number;
|
||||
var prefetchNeeded = true;
|
||||
var columnSortOrder: any;
|
||||
// Threshold(pages) for triggering cache prefetch.
|
||||
// If number remaining pages in cache falls below prefetchThreshold prefetch will be triggered.
|
||||
var prefetchThreshold = 10;
|
||||
var tableQuery = this.tableQuery;
|
||||
|
||||
for (var index in aoData) {
|
||||
var data = aoData[index];
|
||||
if (data.name === "length") {
|
||||
tablePageSize = data.value;
|
||||
}
|
||||
if (data.name === "start") {
|
||||
this.tablePageStartIndex = data.value;
|
||||
}
|
||||
if (data.name === "draw") {
|
||||
draw = data.value;
|
||||
}
|
||||
if (data.name === "order") {
|
||||
columnSortOrder = data.value;
|
||||
}
|
||||
}
|
||||
// Try cache if valid.
|
||||
if (this.isCacheValid(tableQuery)) {
|
||||
// Check if prefetch needed.
|
||||
if (this.tablePageStartIndex + tablePageSize <= this.cache.length || this.allDownloaded) {
|
||||
prefetchNeeded = false;
|
||||
if (columnSortOrder && (!this.cache.sortOrder || !_.isEqual(this.cache.sortOrder, columnSortOrder))) {
|
||||
this.sortColumns(columnSortOrder, oSettings);
|
||||
}
|
||||
this.renderPage(fnCallback, draw, this.tablePageStartIndex, tablePageSize, oSettings);
|
||||
this.tablePageStartIndex = 0;
|
||||
this.renderPage(this.tablePageStartIndex, this.cache.length);
|
||||
if (
|
||||
!this.allDownloaded &&
|
||||
this.tablePageStartIndex > 0 && // This is a case now that we can hit this as we re-construct table when we update column
|
||||
@@ -208,41 +174,21 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
|
||||
if (prefetchNeeded) {
|
||||
var downloadSize = tableQuery.top || this.downloadSize;
|
||||
this.prefetchAndRender(
|
||||
tableQuery,
|
||||
this.tablePageStartIndex,
|
||||
tablePageSize,
|
||||
downloadSize,
|
||||
draw,
|
||||
fnCallback,
|
||||
oSettings,
|
||||
columnSortOrder
|
||||
);
|
||||
return await this.prefetchAndRender(tableQuery, 0, tablePageSize, downloadSize);
|
||||
} else {
|
||||
return this.cache.data;
|
||||
}
|
||||
}
|
||||
|
||||
public addEntityToCache(entity: Entities.ITableEntity): Q.Promise<any> {
|
||||
// Delay the add operation if we are fetching data from server, so as to avoid race condition.
|
||||
if (this.cache.serverCallInProgress) {
|
||||
return Utilities.delay(this.pollingInterval).then(() => {
|
||||
return this.updateCachedEntity(entity);
|
||||
Utilities.delay(this.pollingInterval).then(() => {
|
||||
this.updateCachedEntity(entity);
|
||||
});
|
||||
}
|
||||
|
||||
// Find the first item which is greater than the added entity.
|
||||
var oSettings: any = (<any>this.table).context[0];
|
||||
var index: number = _.findIndex(this.cache.data, (data: any) => {
|
||||
return this.dataComparer(data, entity, this.cache.sortOrder, oSettings) > 0;
|
||||
});
|
||||
|
||||
// If no such item, then insert at last.
|
||||
var insertIndex: number = Utilities.ensureBetweenBounds(
|
||||
index < 0 ? this.cache.length : index,
|
||||
0,
|
||||
this.cache.length
|
||||
);
|
||||
|
||||
this.cache.data.splice(insertIndex, 0, entity);
|
||||
this.cache.data.splice(this.cache.length, 0, entity);
|
||||
|
||||
// Finally, select newly added entity
|
||||
this.clearSelection();
|
||||
@@ -254,8 +200,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
public updateCachedEntity(entity: Entities.ITableEntity): Q.Promise<any> {
|
||||
// Delay the add operation if we are fetching data from server, so as to avoid race condition.
|
||||
if (this.cache.serverCallInProgress) {
|
||||
return Utilities.delay(this.pollingInterval).then(() => {
|
||||
return this.updateCachedEntity(entity);
|
||||
Utilities.delay(this.pollingInterval).then(() => {
|
||||
this.updateCachedEntity(entity);
|
||||
});
|
||||
}
|
||||
var oldEntityIndex: number = _.findIndex(
|
||||
@@ -275,8 +221,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
|
||||
// Delay the remove operation if we are fetching data from server, so as to avoid race condition.
|
||||
if (this.cache.serverCallInProgress) {
|
||||
return Utilities.delay(this.pollingInterval).then(() => {
|
||||
return this.removeEntitiesFromCache(entities);
|
||||
Utilities.delay(this.pollingInterval).then(() => {
|
||||
this.removeEntitiesFromCache(entities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -292,14 +238,6 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
});
|
||||
this.clearSelection();
|
||||
|
||||
// Show last available page if there is not enough data
|
||||
var pageInfo = this.table.page.info();
|
||||
if (this.cache.length <= pageInfo.start) {
|
||||
var availablePages = Math.ceil(this.cache.length / pageInfo.length);
|
||||
var pageToShow = availablePages > 0 ? availablePages - 1 : 0;
|
||||
this.table.page(pageToShow);
|
||||
}
|
||||
|
||||
return Q.resolve(null);
|
||||
}
|
||||
|
||||
@@ -392,86 +330,75 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
private prefetchAndRender(
|
||||
private async prefetchAndRender(
|
||||
tableQuery: Entities.ITableQuery,
|
||||
tablePageStartIndex: number,
|
||||
tablePageSize: number,
|
||||
downloadSize: number,
|
||||
draw: number,
|
||||
renderCallBack: Function,
|
||||
oSettings: any,
|
||||
columnSortOrder: any
|
||||
): void {
|
||||
downloadSize: number
|
||||
): Promise<Entities.ITableEntity[]> {
|
||||
this.queryErrorMessage(null);
|
||||
if (this.cache.serverCallInProgress) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
this.prefetchData(tableQuery, downloadSize, /* currentRetry */ 0)
|
||||
.then((result: IListTableEntitiesSegmentedResult) => {
|
||||
if (!result) {
|
||||
return;
|
||||
try {
|
||||
const result = await this.prefetchData(tableQuery, downloadSize, /* currentRetry */ 0);
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
// Cache is assigned using prefetchData
|
||||
var entities = this.cache.data;
|
||||
if (userContext.apiType === "Cassandra" && DataTableUtilities.checkForDefaultHeader(this.headers)) {
|
||||
(<CassandraAPIDataClient>this.queryTablesTab.container.tableDataClient)
|
||||
.getTableSchema(this.queryTablesTab.collection)
|
||||
.then((headers: CassandraTableKey[]) => {
|
||||
this.updateHeaders(
|
||||
headers.map((header) => header.property),
|
||||
true
|
||||
);
|
||||
});
|
||||
} else {
|
||||
var selectedHeadersUnion: string[] = DataTableUtilities.getPropertyIntersectionFromTableEntities(
|
||||
entities,
|
||||
userContext.apiType === "Cassandra"
|
||||
);
|
||||
var newHeaders: string[] = _.difference(selectedHeadersUnion, this.headers);
|
||||
if (newHeaders.length > 0) {
|
||||
// Any new columns found will be added into headers array, which will trigger a re-render of the DataTable.
|
||||
// So there is no need to call it here.
|
||||
this.updateHeaders(newHeaders, /* notifyColumnChanges */ true);
|
||||
}
|
||||
}
|
||||
this.renderPage(tablePageStartIndex, entities.length);
|
||||
|
||||
var entities = this.cache.data;
|
||||
if (userContext.apiType === "Cassandra" && DataTableUtilities.checkForDefaultHeader(this.headers)) {
|
||||
(<CassandraAPIDataClient>this.queryTablesTab.container.tableDataClient)
|
||||
.getTableSchema(this.queryTablesTab.collection)
|
||||
.then((headers: CassandraTableKey[]) => {
|
||||
this.updateHeaders(
|
||||
headers.map((header) => header.property),
|
||||
true
|
||||
);
|
||||
});
|
||||
} else {
|
||||
var selectedHeadersUnion: string[] = DataTableUtilities.getPropertyIntersectionFromTableEntities(
|
||||
entities,
|
||||
userContext.apiType === "Cassandra"
|
||||
);
|
||||
var newHeaders: string[] = _.difference(selectedHeadersUnion, this.headers);
|
||||
if (newHeaders.length > 0) {
|
||||
// Any new columns found will be added into headers array, which will trigger a re-render of the DataTable.
|
||||
// So there is no need to call it here.
|
||||
this.updateHeaders(newHeaders, /* notifyColumnChanges */ true);
|
||||
} else {
|
||||
if (columnSortOrder) {
|
||||
this.sortColumns(columnSortOrder, oSettings);
|
||||
}
|
||||
this.renderPage(renderCallBack, draw, tablePageStartIndex, tablePageSize, oSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.ExceedMaximumRetries) {
|
||||
var message: string = "We are having trouble getting your data. Please try again."; // localize
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const parsedErrors = parseError(error);
|
||||
var errors = parsedErrors.map((error) => {
|
||||
return <ViewModels.QueryError>{
|
||||
message: error.message,
|
||||
start: error.location ? error.location.start : undefined,
|
||||
end: error.location ? error.location.end : undefined,
|
||||
code: error.code,
|
||||
severity: error.severity,
|
||||
};
|
||||
});
|
||||
this.queryErrorMessage(errors[0].message);
|
||||
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseName: this.queryTablesTab.collection.databaseId,
|
||||
collectionName: this.queryTablesTab.collection.id(),
|
||||
dataExplorerArea: Areas.Tab,
|
||||
tabTitle: this.queryTablesTab.tabTitle(),
|
||||
error: error,
|
||||
},
|
||||
this.queryTablesTab.onLoadStartKey
|
||||
);
|
||||
this.queryTablesTab.onLoadStartKey = null;
|
||||
}
|
||||
DataTableUtilities.turnOffProgressIndicator();
|
||||
return result;
|
||||
} catch (error) {
|
||||
const parsedErrors = parseError(error);
|
||||
var errors = parsedErrors.map((error) => {
|
||||
return <ViewModels.QueryError>{
|
||||
message: error.message,
|
||||
start: error.location ? error.location.start : undefined,
|
||||
end: error.location ? error.location.end : undefined,
|
||||
code: error.code,
|
||||
severity: error.severity,
|
||||
};
|
||||
});
|
||||
this.queryErrorMessage(errors[0].message);
|
||||
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseName: this.queryTablesTab.collection.databaseId,
|
||||
collectionName: this.queryTablesTab.collection.id(),
|
||||
dataExplorerArea: Areas.Tab,
|
||||
tabTitle: this.queryTablesTab.tabTitle(),
|
||||
error: error,
|
||||
},
|
||||
this.queryTablesTab.onLoadStartKey
|
||||
);
|
||||
this.queryTablesTab.onLoadStartKey = null;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,51 +412,52 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
* Note that this also means that we can get less entities than the requested download size in a successful call.
|
||||
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
|
||||
*/
|
||||
private prefetchData(
|
||||
|
||||
private async prefetchData(
|
||||
tableQuery: Entities.ITableQuery,
|
||||
downloadSize: number,
|
||||
currentRetry: number = 0
|
||||
): Q.Promise<any> {
|
||||
): Promise<any> {
|
||||
var entities: any;
|
||||
if (!this.cache.serverCallInProgress) {
|
||||
this.cache.serverCallInProgress = true;
|
||||
this.allDownloaded = false;
|
||||
this.lastPrefetchTime = new Date().getTime();
|
||||
var time = this.lastPrefetchTime;
|
||||
|
||||
var promise: Q.Promise<IListTableEntitiesSegmentedResult>;
|
||||
if (this._documentIterator && this.continuationToken) {
|
||||
// TODO handle Cassandra case
|
||||
|
||||
promise = Q(this._documentIterator.fetchNext().then((response) => response.resources)).then(
|
||||
(documents: any[]) => {
|
||||
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
|
||||
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
|
||||
Results: entities,
|
||||
ContinuationToken: this._documentIterator.hasMoreResults(),
|
||||
};
|
||||
return Q.resolve(finalEntities);
|
||||
}
|
||||
);
|
||||
} else if (this.continuationToken && userContext.apiType === "Cassandra") {
|
||||
promise = Q(
|
||||
this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||
try {
|
||||
if (this._documentIterator && this.continuationToken) {
|
||||
// TODO handle Cassandra case
|
||||
const fetchNext = await this._documentIterator.fetchNext();
|
||||
let fetchNextEntities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(
|
||||
fetchNext.resources
|
||||
);
|
||||
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
|
||||
Results: fetchNextEntities,
|
||||
ContinuationToken: this._documentIterator.hasMoreResults(),
|
||||
};
|
||||
entities = finalEntities;
|
||||
} else if (this.continuationToken && userContext.apiType === "Cassandra") {
|
||||
entities = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||
this.queryTablesTab.collection,
|
||||
this.cqlQuery(),
|
||||
true,
|
||||
this.continuationToken
|
||||
)
|
||||
);
|
||||
} else {
|
||||
let query = this.sqlQuery();
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
query = this.cqlQuery();
|
||||
);
|
||||
} else {
|
||||
let query = this.sqlQuery();
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
query = this.cqlQuery();
|
||||
}
|
||||
entities = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||
this.queryTablesTab.collection,
|
||||
query,
|
||||
true
|
||||
);
|
||||
}
|
||||
promise = Q(
|
||||
this.queryTablesTab.container.tableDataClient.queryDocuments(this.queryTablesTab.collection, query, true)
|
||||
);
|
||||
}
|
||||
return promise
|
||||
.then((result: IListTableEntitiesSegmentedResult) => {
|
||||
|
||||
const result = entities;
|
||||
if (result) {
|
||||
if (!this._documentIterator) {
|
||||
this._documentIterator = result.iterator;
|
||||
}
|
||||
@@ -539,14 +467,14 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
// And as another service call is during process, we don't set serverCallInProgress to false here.
|
||||
// Thus, end the prefetch.
|
||||
if (this.lastPrefetchTime !== time) {
|
||||
return Q.resolve(null);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
var entities = result.Results;
|
||||
actualDownloadSize = entities.length;
|
||||
|
||||
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
|
||||
this.continuationToken = this.isCancelled ? null : result.ContinuationToken;
|
||||
this.continuationToken = this.isCancelled ? undefined : result.ContinuationToken;
|
||||
|
||||
if (!this.continuationToken) {
|
||||
this.allDownloaded = true;
|
||||
@@ -568,30 +496,20 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
this.allDownloaded = true;
|
||||
}
|
||||
|
||||
// There are three possible results for a prefetch:
|
||||
// 1. Continuation token is null or fetched items' size reaches predefined.
|
||||
// 2. Continuation token is not null and fetched items' size hasn't reach predefined.
|
||||
// 2.1 Retry times has reached predefined maximum.
|
||||
// 2.2 Retry times hasn't reached predefined maximum.
|
||||
// Correspondingly,
|
||||
// For #1, end prefetch.
|
||||
// For #2.1, set prefetch exceeds maximum retry number and end prefetch.
|
||||
// For #2.2, go to next round prefetch.
|
||||
if (this.allDownloaded || nextDownloadSize === 0) {
|
||||
return Q.resolve(result);
|
||||
return Promise.resolve(this.cache.data);
|
||||
}
|
||||
|
||||
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
||||
result.ExceedMaximumRetries = true;
|
||||
return Q.resolve(result);
|
||||
return Promise.resolve(this.cache.data);
|
||||
}
|
||||
return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
this.cache.serverCallInProgress = false;
|
||||
return Q.reject(error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.cache.serverCallInProgress = false;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||
|
||||
export interface ITableEntity {
|
||||
[property: string]: ITableEntityAttribute;
|
||||
@@ -17,7 +17,6 @@ export interface ITableEntityAttribute {
|
||||
|
||||
export interface IListTableEntitiesResult {
|
||||
Results: ITableEntity[];
|
||||
//eslint-disable-next-line
|
||||
ContinuationToken: any;
|
||||
iterator?: QueryIterator<ItemDefinition & Resource>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as Utilities from "../Utilities";
|
||||
import QueryClauseViewModel from "./QueryClauseViewModel";
|
||||
import * as Utilities from "../Utilities";
|
||||
|
||||
export default class ClauseGroup {
|
||||
public isRootGroup: boolean;
|
||||
//eslint-disable-next-line
|
||||
public children = new Array();
|
||||
public parentGroup: ClauseGroup;
|
||||
private _id: string;
|
||||
@@ -18,7 +17,7 @@ export default class ClauseGroup {
|
||||
* Flattens the clause tree into an array, depth-first, left to right.
|
||||
*/
|
||||
public flattenClauses(targetArray: ko.ObservableArray<QueryClauseViewModel>): void {
|
||||
const tempArray = new Array<QueryClauseViewModel>();
|
||||
var tempArray = new Array<QueryClauseViewModel>();
|
||||
|
||||
this.flattenClausesImpl(this, tempArray);
|
||||
targetArray.removeAll();
|
||||
@@ -32,10 +31,10 @@ export default class ClauseGroup {
|
||||
newClause.clauseGroup = this;
|
||||
this.children.push(newClause);
|
||||
} else {
|
||||
const targetGroup = insertBefore.clauseGroup;
|
||||
var targetGroup = insertBefore.clauseGroup;
|
||||
|
||||
if (targetGroup) {
|
||||
const insertBeforeIndex = targetGroup.children.indexOf(insertBefore);
|
||||
var insertBeforeIndex = targetGroup.children.indexOf(insertBefore);
|
||||
newClause.clauseGroup = targetGroup;
|
||||
targetGroup.children.splice(insertBeforeIndex, 0, newClause);
|
||||
}
|
||||
@@ -43,19 +42,19 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
public deleteClause(clause: QueryClauseViewModel): void {
|
||||
const targetGroup = clause.clauseGroup;
|
||||
var targetGroup = clause.clauseGroup;
|
||||
|
||||
if (targetGroup) {
|
||||
const index = targetGroup.children.indexOf(clause);
|
||||
var index = targetGroup.children.indexOf(clause);
|
||||
targetGroup.children.splice(index, 1);
|
||||
clause.dispose();
|
||||
|
||||
if (targetGroup.children.length <= 1 && !targetGroup.isRootGroup) {
|
||||
const parent = targetGroup.parentGroup;
|
||||
const targetGroupIndex = parent.children.indexOf(targetGroup);
|
||||
var parent = targetGroup.parentGroup;
|
||||
var targetGroupIndex = parent.children.indexOf(targetGroup);
|
||||
|
||||
if (targetGroup.children.length === 1) {
|
||||
const orphan = targetGroup.children.shift();
|
||||
var orphan = targetGroup.children.shift();
|
||||
|
||||
if (orphan instanceof QueryClauseViewModel) {
|
||||
(<QueryClauseViewModel>orphan).clauseGroup = parent;
|
||||
@@ -72,14 +71,14 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
public removeAll(): void {
|
||||
const allClauses: QueryClauseViewModel[] = new Array<QueryClauseViewModel>();
|
||||
var allClauses: QueryClauseViewModel[] = new Array<QueryClauseViewModel>();
|
||||
|
||||
this.flattenClausesImpl(this, allClauses);
|
||||
|
||||
while (allClauses.length > 0) {
|
||||
allClauses.shift().dispose();
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
|
||||
this.children = new Array<any>();
|
||||
}
|
||||
|
||||
@@ -88,12 +87,12 @@ export default class ClauseGroup {
|
||||
*/
|
||||
public groupSelectedItems(): boolean {
|
||||
// Find the selection start & end, also check for gaps between selected items (if found, cannot proceed).
|
||||
const selection = this.getCheckedItemsInfo();
|
||||
var selection = this.getCheckedItemsInfo();
|
||||
|
||||
if (selection.canGroup) {
|
||||
const newGroup = new ClauseGroup(false, this);
|
||||
var newGroup = new ClauseGroup(false, this);
|
||||
// Replace the selected items with the new group, and then move the selected items into the new group.
|
||||
const groupedItems = this.children.splice(selection.begin, selection.end - selection.begin + 1, newGroup);
|
||||
var groupedItems = this.children.splice(selection.begin, selection.end - selection.begin + 1, newGroup);
|
||||
|
||||
groupedItems &&
|
||||
groupedItems.forEach((element) => {
|
||||
@@ -119,13 +118,13 @@ export default class ClauseGroup {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentGroup = this.parentGroup;
|
||||
let index = parentGroup.children.indexOf(this);
|
||||
var parentGroup = this.parentGroup;
|
||||
var index = parentGroup.children.indexOf(this);
|
||||
|
||||
if (index >= 0) {
|
||||
parentGroup.children.splice(index, 1);
|
||||
|
||||
const toPromote = this.children.splice(0, this.children.length);
|
||||
var toPromote = this.children.splice(0, this.children.length);
|
||||
|
||||
// Move all children one level up.
|
||||
toPromote &&
|
||||
@@ -147,16 +146,16 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
public findDeepestGroupInChildren(skipIndex?: number): ClauseGroup {
|
||||
let deepest = <ClauseGroup>this;
|
||||
let level = 0;
|
||||
const func = (currentGroup: ClauseGroup): void => {
|
||||
var deepest: ClauseGroup = this;
|
||||
var level: number = 0;
|
||||
var func = (currentGroup: ClauseGroup): void => {
|
||||
level++;
|
||||
if (currentGroup.getCurrentGroupDepth() > deepest.getCurrentGroupDepth()) {
|
||||
deepest = currentGroup;
|
||||
}
|
||||
|
||||
for (let i = 0; i < currentGroup.children.length; i++) {
|
||||
const currentItem = currentGroup.children[i];
|
||||
for (var i = 0; i < currentGroup.children.length; i++) {
|
||||
var currentItem = currentGroup.children[i];
|
||||
|
||||
if ((i !== skipIndex || level > 1) && currentItem instanceof ClauseGroup) {
|
||||
func(currentItem);
|
||||
@@ -171,16 +170,16 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
private getCheckedItemsInfo(): { canGroup: boolean; begin: number; end: number } {
|
||||
let beginIndex = -1;
|
||||
let endIndex = -1;
|
||||
var beginIndex = -1;
|
||||
var endIndex = -1;
|
||||
// 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.
|
||||
let gapFlag = false;
|
||||
let count = 0;
|
||||
var gapFlag = false;
|
||||
var count = 0;
|
||||
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const currentItem = this.children[i];
|
||||
let subGroupSelectionState: { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean };
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var currentItem = this.children[i];
|
||||
var subGroupSelectionState: { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean };
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
subGroupSelectionState = (<ClauseGroup>currentItem).getSelectionState();
|
||||
@@ -236,10 +235,10 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
private getSelectionState(): { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean } {
|
||||
let selectedCount = 0;
|
||||
var selectedCount = 0;
|
||||
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const currentItem = this.children[i];
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var currentItem = this.children[i];
|
||||
|
||||
if (currentItem instanceof ClauseGroup && (<ClauseGroup>currentItem).getSelectionState().allSelected) {
|
||||
selectedCount++;
|
||||
@@ -261,8 +260,8 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
private unselectAll(): void {
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const currentItem = this.children[i];
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var currentItem = this.children[i];
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
(<ClauseGroup>currentItem).unselectAll();
|
||||
@@ -279,8 +278,8 @@ export default class ClauseGroup {
|
||||
targetArray.splice(0, targetArray.length);
|
||||
}
|
||||
|
||||
for (let i = 0; i < queryGroup.children.length; i++) {
|
||||
const currentItem = queryGroup.children[i];
|
||||
for (var i = 0; i < queryGroup.children.length; i++) {
|
||||
var currentItem = queryGroup.children[i];
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
this.flattenClausesImpl(currentItem, targetArray);
|
||||
@@ -293,13 +292,13 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
public getTreeDepth(): number {
|
||||
let currentDepth = this.getCurrentGroupDepth();
|
||||
var currentDepth = this.getCurrentGroupDepth();
|
||||
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const currentItem = this.children[i];
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var currentItem = this.children[i];
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
const newDepth = (<ClauseGroup>currentItem).getTreeDepth();
|
||||
var newDepth = (<ClauseGroup>currentItem).getTreeDepth();
|
||||
|
||||
if (newDepth > currentDepth) {
|
||||
currentDepth = newDepth;
|
||||
@@ -311,8 +310,8 @@ export default class ClauseGroup {
|
||||
}
|
||||
|
||||
public getCurrentGroupDepth(): number {
|
||||
let group = <ClauseGroup>this;
|
||||
let depth = 0;
|
||||
var group = <ClauseGroup>this;
|
||||
var depth = 0;
|
||||
|
||||
while (!group.isRootGroup) {
|
||||
depth++;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../Constants";
|
||||
import ClauseGroup from "./ClauseGroup";
|
||||
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.
|
||||
@@ -38,7 +38,7 @@ export default class ClauseGroupViewModel {
|
||||
};
|
||||
|
||||
private getGroupBackgroundColor(group: ClauseGroup): string {
|
||||
const colorCount = Constants.clauseGroupColors.length;
|
||||
var colorCount = Constants.clauseGroupColors.length;
|
||||
|
||||
if (group.isRootGroup) {
|
||||
return Constants.transparentColor;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as ko from "knockout";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { IQueryTableRowsType } from "../../Tabs/QueryTablesTab/QueryTableTabUtils";
|
||||
import * as Constants from "../Constants";
|
||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||
@@ -29,7 +30,7 @@ export default class QueryBuilderViewModel {
|
||||
public removeThisFilterLine = "Remove this filter line"; // 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 queryClauses = new ClauseGroup(true, undefined); // The actual data structure containing the clause information.
|
||||
public queryClauses = new ClauseGroup(true, null); // The actual data structure containing the clause information.
|
||||
public columnOptions: ko.ObservableArray<string>;
|
||||
public canGroupClauses = ko.observable<boolean>(false);
|
||||
|
||||
@@ -106,14 +107,14 @@ export default class QueryBuilderViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public setExample() {
|
||||
const example1 = new QueryClauseViewModel(
|
||||
public setExample(pk: string, rk: string) {
|
||||
var example1 = new QueryClauseViewModel(
|
||||
this,
|
||||
"",
|
||||
"PartitionKey",
|
||||
this.edmTypes()[0],
|
||||
Constants.Operator.Equal,
|
||||
this.tableEntityListViewModel.items()[0].PartitionKey._,
|
||||
pk,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
@@ -121,13 +122,13 @@ export default class QueryBuilderViewModel {
|
||||
//null,
|
||||
true
|
||||
);
|
||||
const example2 = new QueryClauseViewModel(
|
||||
var example2 = new QueryClauseViewModel(
|
||||
this,
|
||||
"And",
|
||||
"RowKey",
|
||||
this.edmTypes()[0],
|
||||
Constants.Operator.Equal,
|
||||
this.tableEntityListViewModel.items()[0].RowKey._,
|
||||
rk,
|
||||
true,
|
||||
"",
|
||||
"",
|
||||
@@ -139,41 +140,32 @@ export default class QueryBuilderViewModel {
|
||||
this.addClauseImpl(example2, 1);
|
||||
}
|
||||
|
||||
public getODataFilterFromClauses = (): string => {
|
||||
let filterString = "";
|
||||
const treeTraversal = (group: ClauseGroup): void => {
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
const currentItem = group.children[i];
|
||||
public getODataFilterFromClauses = (queryClauses: IQueryTableRowsType[]): string => {
|
||||
var filterString: string = "";
|
||||
if (queryClauses != undefined) {
|
||||
for (var i = 0; i < queryClauses.length; i++) {
|
||||
var currentItem = queryClauses[i];
|
||||
|
||||
if (currentItem instanceof QueryClauseViewModel) {
|
||||
const clause = <QueryClauseViewModel>currentItem;
|
||||
this.timestampToValue(clause);
|
||||
filterString = filterString.concat(
|
||||
this.constructODataClause(
|
||||
filterString === "" ? "" : clause.and_or(),
|
||||
this.generateLeftParentheses(clause),
|
||||
clause.field(),
|
||||
clause.type(),
|
||||
clause.operator(),
|
||||
clause.value(),
|
||||
this.generateRightParentheses(clause)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
treeTraversal(<ClauseGroup>currentItem);
|
||||
}
|
||||
this.timestampToValue(currentItem);
|
||||
filterString = filterString.concat(
|
||||
this.constructODataClause(
|
||||
filterString === "" ? "" : currentItem.selectedOperation,
|
||||
this.generateLeftParentheses(currentItem),
|
||||
currentItem.selectedField,
|
||||
currentItem.selectedEntityType,
|
||||
currentItem.selectedOperator,
|
||||
currentItem.entityValue,
|
||||
this.generateRightParentheses(currentItem)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
treeTraversal(this.queryClauses);
|
||||
}
|
||||
|
||||
return filterString.trim();
|
||||
};
|
||||
|
||||
public getSqlFilterFromClauses = (): string => {
|
||||
let filterString = "SELECT * FROM c";
|
||||
public getSqlFilterFromClauses = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||
var filterString: string = "SELECT * FROM c";
|
||||
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
||||
filterString = "SELECT";
|
||||
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
|
||||
@@ -195,52 +187,41 @@ export default class QueryBuilderViewModel {
|
||||
});
|
||||
filterString = filterString.concat(" FROM c");
|
||||
}
|
||||
if (this.queryClauses.children.length === 0) {
|
||||
if (queryTableRows.length === 0) {
|
||||
return filterString;
|
||||
}
|
||||
filterString = filterString.concat(" WHERE");
|
||||
let first = true;
|
||||
const treeTraversal = (group: ClauseGroup): void => {
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
const currentItem = group.children[i];
|
||||
var first = true;
|
||||
for (var i = 0; i < queryTableRows.length; i++) {
|
||||
var currentItem = queryTableRows[i];
|
||||
|
||||
if (currentItem instanceof QueryClauseViewModel) {
|
||||
const clause = <QueryClauseViewModel>currentItem;
|
||||
const timeStampValue: string = this.timestampToSqlValue(clause);
|
||||
let value = clause.value();
|
||||
if (!clause.isValue()) {
|
||||
value = timeStampValue;
|
||||
}
|
||||
filterString = filterString.concat(
|
||||
this.constructSqlClause(
|
||||
first ? "" : clause.and_or(),
|
||||
this.generateLeftParentheses(clause),
|
||||
clause.field(),
|
||||
clause.type(),
|
||||
clause.operator(),
|
||||
value,
|
||||
this.generateRightParentheses(clause)
|
||||
)
|
||||
);
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
treeTraversal(<ClauseGroup>currentItem);
|
||||
}
|
||||
let timeStampValue: string = this.timestampToSqlValue(currentItem);
|
||||
var value = currentItem.entityValue;
|
||||
if (!currentItem.isValue) {
|
||||
value = timeStampValue;
|
||||
}
|
||||
};
|
||||
|
||||
treeTraversal(this.queryClauses);
|
||||
filterString = filterString.concat(
|
||||
this.constructSqlClause(
|
||||
first ? "" : currentItem.selectedOperation,
|
||||
this.generateLeftParentheses(currentItem),
|
||||
currentItem.selectedField,
|
||||
currentItem.selectedEntityType,
|
||||
currentItem.selectedOperator,
|
||||
value,
|
||||
this.generateRightParentheses(currentItem)
|
||||
)
|
||||
);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return filterString.trim();
|
||||
};
|
||||
|
||||
public getCqlFilterFromClauses = (): string => {
|
||||
public getCqlFilterFromClauses = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
|
||||
const collectionId = this._queryViewModel.queryTablesTab.collection.id();
|
||||
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
|
||||
let filterString = `SELECT * FROM ${tableToQuery}`;
|
||||
var filterString: string = `SELECT * FROM ${tableToQuery}`;
|
||||
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
||||
filterString = "SELECT";
|
||||
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
|
||||
@@ -251,55 +232,44 @@ export default class QueryBuilderViewModel {
|
||||
});
|
||||
filterString = filterString.concat(` FROM ${tableToQuery}`);
|
||||
}
|
||||
if (this.queryClauses.children.length === 0) {
|
||||
if (queryTableRows === undefined || queryTableRows.length === 0) {
|
||||
return filterString;
|
||||
}
|
||||
filterString = filterString.concat(" WHERE");
|
||||
let first = true;
|
||||
const treeTraversal = (group: ClauseGroup): void => {
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
const currentItem = group.children[i];
|
||||
var first = true;
|
||||
for (var i = 0; i < queryTableRows.length; i++) {
|
||||
var currentItem = queryTableRows[i];
|
||||
|
||||
if (currentItem instanceof QueryClauseViewModel) {
|
||||
const clause = <QueryClauseViewModel>currentItem;
|
||||
const timeStampValue = this.timestampToSqlValue(clause);
|
||||
let value = clause.value();
|
||||
if (!clause.isValue()) {
|
||||
value = timeStampValue;
|
||||
}
|
||||
filterString = filterString.concat(
|
||||
this.constructCqlClause(
|
||||
first ? "" : clause.and_or(),
|
||||
this.generateLeftParentheses(clause),
|
||||
clause.field(),
|
||||
clause.type(),
|
||||
clause.operator(),
|
||||
value,
|
||||
this.generateRightParentheses(clause)
|
||||
)
|
||||
);
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (currentItem instanceof ClauseGroup) {
|
||||
treeTraversal(<ClauseGroup>currentItem);
|
||||
}
|
||||
let timeStampValue: string = this.timestampToSqlValue(currentItem);
|
||||
var value = currentItem.entityValue;
|
||||
if (!currentItem.isValue) {
|
||||
value = timeStampValue;
|
||||
}
|
||||
};
|
||||
|
||||
treeTraversal(this.queryClauses);
|
||||
filterString = filterString.concat(
|
||||
this.constructCqlClause(
|
||||
first ? "" : currentItem.selectedOperation,
|
||||
this.generateLeftParentheses(currentItem),
|
||||
currentItem.selectedField,
|
||||
currentItem.selectedEntityType,
|
||||
currentItem.selectedOperator,
|
||||
value,
|
||||
this.generateRightParentheses(currentItem)
|
||||
)
|
||||
);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return filterString.trim();
|
||||
};
|
||||
|
||||
public updateColumnOptions = (): void => {
|
||||
// let originalHeaders = this.columnOptions();
|
||||
const newHeaders = this.tableEntityListViewModel.headers;
|
||||
let originalHeaders = this.columnOptions();
|
||||
let newHeaders = this.tableEntityListViewModel.headers;
|
||||
this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns));
|
||||
};
|
||||
|
||||
private generateLeftParentheses(clause: QueryClauseViewModel): string {
|
||||
let result = "";
|
||||
private generateLeftParentheses(clause: IQueryTableRowsType): string {
|
||||
var result = "";
|
||||
|
||||
if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) {
|
||||
return result;
|
||||
@@ -307,7 +277,7 @@ export default class QueryBuilderViewModel {
|
||||
result = result.concat("(");
|
||||
}
|
||||
|
||||
let currentGroup: ClauseGroup = clause.clauseGroup;
|
||||
var currentGroup: ClauseGroup = clause.clauseGroup;
|
||||
|
||||
while (
|
||||
!currentGroup.isRootGroup &&
|
||||
@@ -321,8 +291,8 @@ export default class QueryBuilderViewModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
private generateRightParentheses(clause: QueryClauseViewModel): string {
|
||||
let result = "";
|
||||
private generateRightParentheses(clause: IQueryTableRowsType): string {
|
||||
var result = "";
|
||||
|
||||
if (
|
||||
clause.clauseGroup.isRootGroup ||
|
||||
@@ -333,7 +303,7 @@ export default class QueryBuilderViewModel {
|
||||
result = result.concat(")");
|
||||
}
|
||||
|
||||
let currentGroup: ClauseGroup = clause.clauseGroup;
|
||||
var currentGroup: ClauseGroup = clause.clauseGroup;
|
||||
|
||||
while (
|
||||
!currentGroup.isRootGroup &&
|
||||
@@ -364,17 +334,14 @@ export default class QueryBuilderViewModel {
|
||||
case Constants.TableType.String:
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
||||
operator
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
)} \'${value}\'${rightParentheses}`;
|
||||
case Constants.TableType.Guid:
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
||||
operator
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
)} guid\'${value}\'${rightParentheses}`;
|
||||
case Constants.TableType.Binary:
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
||||
operator
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
)} binary\'${value}\'${rightParentheses}`;
|
||||
default:
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
||||
@@ -394,11 +361,9 @@ export default class QueryBuilderViewModel {
|
||||
): string => {
|
||||
if (propertyName === Constants.EntityKeyNames.PartitionKey) {
|
||||
propertyName = TableEntityProcessor.keyProperties.PartitionKey;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c["${propertyName}"] ${operator} \'${value}\'${rightParentheses}`;
|
||||
} else if (propertyName === Constants.EntityKeyNames.RowKey) {
|
||||
propertyName = TableEntityProcessor.keyProperties.Id;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} \'${value}\'${rightParentheses}`;
|
||||
} else if (propertyName === Constants.EntityKeyNames.Timestamp) {
|
||||
propertyName = TableEntityProcessor.keyProperties.Timestamp;
|
||||
@@ -408,21 +373,16 @@ export default class QueryBuilderViewModel {
|
||||
}
|
||||
switch (type) {
|
||||
case Constants.TableType.DateTime:
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${DateTimeUtilities.convertJSDateToTicksWithPadding(
|
||||
value
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
)}\'${rightParentheses}`;
|
||||
case Constants.TableType.Int64:
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${Utilities.padLongWithZeros(
|
||||
value
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
)}\'${rightParentheses}`;
|
||||
case Constants.TableType.String:
|
||||
case Constants.TableType.Guid:
|
||||
case Constants.TableType.Binary:
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${value}\'${rightParentheses}`;
|
||||
default:
|
||||
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} ${value}${rightParentheses}`;
|
||||
@@ -444,7 +404,6 @@ export default class QueryBuilderViewModel {
|
||||
type === Constants.CassandraType.Ascii ||
|
||||
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}`;
|
||||
@@ -465,7 +424,7 @@ export default class QueryBuilderViewModel {
|
||||
case Constants.Operator.NotEqualTo:
|
||||
return Constants.ODataOperator.NotEqualTo;
|
||||
}
|
||||
return undefined;
|
||||
return null;
|
||||
};
|
||||
|
||||
public groupClauses = (): void => {
|
||||
@@ -474,11 +433,11 @@ export default class QueryBuilderViewModel {
|
||||
this.updateCanGroupClauses();
|
||||
};
|
||||
|
||||
public addClauseIndex = (index: number): void => {
|
||||
public addClauseIndex = (index: number, data: any): void => {
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
const newClause = new QueryClauseViewModel(
|
||||
var newClause = new QueryClauseViewModel(
|
||||
this,
|
||||
"And",
|
||||
"",
|
||||
@@ -503,28 +462,28 @@ export default class QueryBuilderViewModel {
|
||||
|
||||
// adds a new clause to the end of the array
|
||||
public addNewClause = (): void => {
|
||||
this.addClauseIndex(this.clauseArray().length);
|
||||
this.addClauseIndex(this.clauseArray().length, null);
|
||||
};
|
||||
|
||||
public onAddClauseKeyDown = (index: number, event: KeyboardEvent): boolean => {
|
||||
public onAddClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.addClauseIndex(index);
|
||||
this.addClauseIndex(index, data);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
|
||||
public onAddNewClauseKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.addClauseIndex(this.clauseArray().length - 1);
|
||||
this.addClauseIndex(this.clauseArray().length - 1, null);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public deleteClause = (index: number): void => {
|
||||
public deleteClause = (index: number, data: any): void => {
|
||||
this.deleteClauseImpl(index);
|
||||
if (this.clauseArray().length !== 0) {
|
||||
this.clauseArray()[0].and_or("");
|
||||
@@ -534,9 +493,9 @@ export default class QueryBuilderViewModel {
|
||||
$(window).resize();
|
||||
};
|
||||
|
||||
public onDeleteClauseKeyDown = (index: number, event: KeyboardEvent): boolean => {
|
||||
public onDeleteClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.deleteClause(index);
|
||||
this.deleteClause(index, data);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
@@ -550,26 +509,25 @@ export default class QueryBuilderViewModel {
|
||||
* (transparent) or its parent group view models.
|
||||
*/
|
||||
public getClauseGroupViewModels = (clause: QueryClauseViewModel): ClauseGroupViewModel[] => {
|
||||
const placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this);
|
||||
const treeDepth = this.queryClauses.getTreeDepth();
|
||||
const groupViewModels = new Array<ClauseGroupViewModel>(treeDepth);
|
||||
var placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this);
|
||||
var treeDepth = this.queryClauses.getTreeDepth();
|
||||
var groupViewModels = new Array<ClauseGroupViewModel>(treeDepth);
|
||||
|
||||
// Prefill the arry with placeholders.
|
||||
for (let i = 0; i < groupViewModels.length; i++) {
|
||||
for (var i = 0; i < groupViewModels.length; i++) {
|
||||
groupViewModels[i] = placeHolderGroupViewModel;
|
||||
}
|
||||
|
||||
let currentGroup = clause.clauseGroup;
|
||||
var currentGroup = clause.clauseGroup;
|
||||
|
||||
// This function determines whether the path from clause to the current group is on the left most.
|
||||
const isLeftMostPath = (): boolean => {
|
||||
let group = clause.clauseGroup;
|
||||
var isLeftMostPath = (): boolean => {
|
||||
var group = clause.clauseGroup;
|
||||
|
||||
if (group.children.indexOf(clause) !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (group.getId() === currentGroup.getId()) {
|
||||
break;
|
||||
@@ -585,14 +543,13 @@ export default class QueryBuilderViewModel {
|
||||
};
|
||||
|
||||
// This function determines whether the path from clause to the current group is on the right most.
|
||||
const isRightMostPath = (): boolean => {
|
||||
let group = clause.clauseGroup;
|
||||
var isRightMostPath = (): boolean => {
|
||||
var group = clause.clauseGroup;
|
||||
|
||||
if (group.children.indexOf(clause) !== group.children.length - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (group.getId() === currentGroup.getId()) {
|
||||
break;
|
||||
@@ -607,26 +564,26 @@ export default class QueryBuilderViewModel {
|
||||
return true;
|
||||
};
|
||||
|
||||
let vmIndex = groupViewModels.length - 1;
|
||||
let skipIndex = -1;
|
||||
let lastDepth = clause.groupDepth;
|
||||
var vmIndex = groupViewModels.length - 1;
|
||||
var skipIndex = -1;
|
||||
var lastDepth = clause.groupDepth;
|
||||
|
||||
while (!currentGroup.isRootGroup) {
|
||||
// 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
|
||||
// current & deepest sibling.
|
||||
const deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth();
|
||||
var deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth();
|
||||
// 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
|
||||
// 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
|
||||
// sub-group, we only need to draw the group block once.
|
||||
const repeatCount = Math.max(deepestInSiblings - lastDepth, 0);
|
||||
var repeatCount = Math.max(deepestInSiblings - lastDepth, 0);
|
||||
|
||||
for (let i = 0; i <= repeatCount; i++) {
|
||||
const isLeftMost = isLeftMostPath();
|
||||
const isRightMost = isRightMostPath();
|
||||
const groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this);
|
||||
for (var i = 0; i <= repeatCount; i++) {
|
||||
var isLeftMost = isLeftMostPath();
|
||||
var isRightMost = isRightMostPath();
|
||||
var groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this);
|
||||
|
||||
groupViewModel.showTopBorder(isLeftMost);
|
||||
groupViewModel.showBottomBorder(isRightMost);
|
||||
@@ -643,16 +600,11 @@ export default class QueryBuilderViewModel {
|
||||
return groupViewModels;
|
||||
};
|
||||
|
||||
public runQuery = (): DataTables.DataTable => {
|
||||
return this._queryViewModel.runQuery();
|
||||
};
|
||||
|
||||
public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void {
|
||||
const index = this.clauseArray.peek().indexOf(clauseToAdd);
|
||||
var index = this.clauseArray.peek().indexOf(clauseToAdd);
|
||||
|
||||
const newClause = new QueryClauseViewModel(
|
||||
var newClause = new QueryClauseViewModel(
|
||||
this,
|
||||
//this._tableEntityListViewModel.tableExplorerContext.hostProxy,
|
||||
"And",
|
||||
clauseToAdd.field(),
|
||||
"DateTime",
|
||||
@@ -675,10 +627,10 @@ export default class QueryBuilderViewModel {
|
||||
}
|
||||
|
||||
private scrollToBottom(): void {
|
||||
const scrollBox = document.getElementById("scroll");
|
||||
var scrollBox = document.getElementById("scroll");
|
||||
if (!this.scrollEventListener) {
|
||||
scrollBox.addEventListener("scroll", function () {
|
||||
const translate = "translate(0," + this.scrollTop + "px)";
|
||||
var translate = "translate(0," + this.scrollTop + "px)";
|
||||
const allTh = <NodeListOf<HTMLElement>>this.querySelectorAll("thead td");
|
||||
for (let i = 0; i < allTh.length; i++) {
|
||||
allTh[i].style.transform = translate;
|
||||
@@ -686,7 +638,7 @@ export default class QueryBuilderViewModel {
|
||||
});
|
||||
this.scrollEventListener = true;
|
||||
}
|
||||
const isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1;
|
||||
var isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1;
|
||||
if (isScrolledToBottom) {
|
||||
scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.clientHeight;
|
||||
}
|
||||
@@ -698,8 +650,8 @@ export default class QueryBuilderViewModel {
|
||||
}
|
||||
|
||||
private deleteClauseImpl(index: number): void {
|
||||
const clause = this.clauseArray()[index];
|
||||
const previousClause = index === 0 ? 0 : index - 1;
|
||||
var clause = this.clauseArray()[index];
|
||||
var previousClause = index === 0 ? 0 : index - 1;
|
||||
this.queryClauses.deleteClause(clause);
|
||||
this.updateClauseArray();
|
||||
if (this.clauseArray()[previousClause]) {
|
||||
@@ -726,67 +678,67 @@ export default class QueryBuilderViewModel {
|
||||
//DataTableUtilities.forceRecalculateTableSize();
|
||||
}
|
||||
|
||||
private timestampToValue(clause: QueryClauseViewModel): void {
|
||||
if (clause.isValue()) {
|
||||
private timestampToValue(clause: IQueryTableRowsType): void {
|
||||
if (clause.isValue) {
|
||||
return;
|
||||
} else if (clause.isTimestamp()) {
|
||||
} else if (clause.isTimestamp) {
|
||||
this.getTimeStampToQuery(clause);
|
||||
// } else if (clause.isCustomLastTimestamp()) {
|
||||
// clause.value(`datetime'${CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit)}'`);
|
||||
} else if (clause.isCustomRangeTimestamp()) {
|
||||
if (clause.isLocal()) {
|
||||
clause.value(`datetime'${DateTimeUtilities.getUTCDateTime(clause.customTimeValue())}'`);
|
||||
} else if (clause.isCustomRangeTimestamp) {
|
||||
if (clause.isLocal) {
|
||||
clause.entityValue = `datetime'${DateTimeUtilities.getUTCDateTime(clause.customTimeValue)}'`;
|
||||
} else {
|
||||
clause.value(`datetime'${clause.customTimeValue()}Z'`);
|
||||
clause.entityValue = `datetime'${clause.customTimeValue}Z'`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private timestampToSqlValue(clause: QueryClauseViewModel): string {
|
||||
if (clause.isValue()) {
|
||||
return undefined;
|
||||
} else if (clause.isTimestamp()) {
|
||||
private timestampToSqlValue(clause: IQueryTableRowsType): string {
|
||||
if (clause.isValue) {
|
||||
return null;
|
||||
} else if (clause.isTimestamp) {
|
||||
return this.getTimeStampToSqlQuery(clause);
|
||||
// } else if (clause.isCustomLastTimestamp()) {
|
||||
// clause.value(CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit));
|
||||
} else if (clause.isCustomRangeTimestamp()) {
|
||||
if (clause.isLocal()) {
|
||||
return DateTimeUtilities.getUTCDateTime(clause.customTimeValue());
|
||||
} else if (clause.isCustomRangeTimestamp) {
|
||||
if (clause.isLocal) {
|
||||
return DateTimeUtilities.getUTCDateTime(clause.customTimeValue);
|
||||
} else {
|
||||
return clause.customTimeValue();
|
||||
return clause.customTimeValue;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
private getTimeStampToQuery(clause: QueryClauseViewModel): void {
|
||||
switch (clause.timeValue()) {
|
||||
private getTimeStampToQuery(clause: IQueryTableRowsType): void {
|
||||
switch (clause.timeValue) {
|
||||
case Constants.timeOptions.lastHour:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(0, 1)}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(0, 1)}'`;
|
||||
break;
|
||||
case Constants.timeOptions.last24Hours:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(0, 24)}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(0, 24)}'`;
|
||||
break;
|
||||
case Constants.timeOptions.last7Days:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(7, 0)}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(7, 0)}'`;
|
||||
break;
|
||||
case Constants.timeOptions.last31Days:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(31, 0)}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(31, 0)}'`;
|
||||
break;
|
||||
case Constants.timeOptions.last365Days:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(365, 0)}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(365, 0)}'`;
|
||||
break;
|
||||
case Constants.timeOptions.currentMonth:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryCurrentMonthLocal()}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryCurrentMonthLocal()}'`;
|
||||
break;
|
||||
case Constants.timeOptions.currentYear:
|
||||
clause.value(`datetime'${CustomTimestampHelper._queryCurrentYearLocal()}'`);
|
||||
clause.entityValue = `datetime'${CustomTimestampHelper._queryCurrentYearLocal()}'`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private getTimeStampToSqlQuery(clause: QueryClauseViewModel): string {
|
||||
switch (clause.timeValue()) {
|
||||
private getTimeStampToSqlQuery(clause: IQueryTableRowsType): string {
|
||||
switch (clause.timeValue) {
|
||||
case Constants.timeOptions.lastHour:
|
||||
return CustomTimestampHelper._queryLastDaysHours(0, 1);
|
||||
case Constants.timeOptions.last24Hours:
|
||||
@@ -802,7 +754,7 @@ export default class QueryBuilderViewModel {
|
||||
case Constants.timeOptions.currentYear:
|
||||
return CustomTimestampHelper._queryCurrentYearLocal();
|
||||
}
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
public checkIfClauseChanged(): void {
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class QueryClauseViewModel {
|
||||
public field: ko.Observable<string>;
|
||||
public type: ko.Observable<string>;
|
||||
public operator: ko.Observable<string>;
|
||||
public value: ko.Observable<string>;
|
||||
public value: ko.Observable<any>;
|
||||
public timeValue: ko.Observable<string>;
|
||||
public customTimeValue: ko.Observable<string>;
|
||||
public canAnd: ko.Observable<boolean>;
|
||||
@@ -39,7 +39,7 @@ export default class QueryClauseViewModel {
|
||||
field: string,
|
||||
type: string,
|
||||
operator: string,
|
||||
value: string,
|
||||
value: any,
|
||||
canAnd: boolean,
|
||||
timeValue: string,
|
||||
customTimeValue: string,
|
||||
@@ -88,30 +88,30 @@ export default class QueryClauseViewModel {
|
||||
userContext.apiType !== "Cassandra"
|
||||
);
|
||||
|
||||
this.and_or.subscribe(() => {
|
||||
this.and_or.subscribe((value) => {
|
||||
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||
});
|
||||
this.field.subscribe(() => {
|
||||
this.field.subscribe((value) => {
|
||||
this.changeField();
|
||||
});
|
||||
this.type.subscribe(() => {
|
||||
this.type.subscribe((value) => {
|
||||
this.changeType();
|
||||
});
|
||||
this.timeValue.subscribe(() => {
|
||||
this.timeValue.subscribe((value) => {
|
||||
// if (this.timeValue() === QueryBuilderConstants.timeOptions.custom) {
|
||||
// this.customTimestampDialog();
|
||||
// }
|
||||
});
|
||||
this.customTimeValue.subscribe(() => {
|
||||
this.customTimeValue.subscribe((value) => {
|
||||
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||
});
|
||||
this.value.subscribe(() => {
|
||||
this.value.subscribe((value) => {
|
||||
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||
});
|
||||
this.operator.subscribe(() => {
|
||||
this.operator.subscribe((value) => {
|
||||
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||
});
|
||||
this._groupCheckSubscription = this.checkedForGrouping.subscribe(() => {
|
||||
this._groupCheckSubscription = this.checkedForGrouping.subscribe((value) => {
|
||||
this._queryBuilderViewModel.updateCanGroupClauses();
|
||||
});
|
||||
this.isAndOrFocused = ko.observable<boolean>(false);
|
||||
@@ -280,7 +280,7 @@ export default class QueryClauseViewModel {
|
||||
this._groupCheckSubscription.dispose();
|
||||
}
|
||||
|
||||
this.clauseGroup = undefined;
|
||||
this._queryBuilderViewModel = undefined;
|
||||
this.clauseGroup = null;
|
||||
this._queryBuilderViewModel = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { KeyCodes } from "../../../Common/Constants";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import { IQueryTableRowsType } from "../../Tabs/QueryTablesTab/QueryTableTabUtils";
|
||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
||||
@@ -39,14 +40,14 @@ export default class QueryViewModel {
|
||||
|
||||
public columnOptions: ko.ObservableArray<string>;
|
||||
|
||||
public queryTablesTab: QueryTablesTab;
|
||||
public queryTablesTab: NewQueryTablesTab;
|
||||
public id: string;
|
||||
private _tableEntityListViewModel: TableEntityListViewModel;
|
||||
|
||||
constructor(queryTablesTab: QueryTablesTab) {
|
||||
constructor(queryTablesTab: NewQueryTablesTab) {
|
||||
this.queryTablesTab = queryTablesTab;
|
||||
this.id = `queryViewModel${this.queryTablesTab.tabId}`;
|
||||
this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel();
|
||||
this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel;
|
||||
|
||||
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
||||
return userContext.apiType !== "Cassandra";
|
||||
@@ -103,7 +104,7 @@ export default class QueryViewModel {
|
||||
DataTableUtilities.forceRecalculateTableSize();
|
||||
};
|
||||
|
||||
public toggleAdvancedOptions = () => {
|
||||
public toggleAdvancedOptions = (): void => {
|
||||
this.isExpanded(!this.isExpanded());
|
||||
if (this.isExpanded()) {
|
||||
this.focusTopResult(true);
|
||||
@@ -126,23 +127,19 @@ export default class QueryViewModel {
|
||||
return this.selectText();
|
||||
};
|
||||
|
||||
private setFilter = (): string => {
|
||||
private setFilter = (queryTableRows?: IQueryTableRowsType[]): string => {
|
||||
const queryString = this.isEditorActive()
|
||||
? this.queryText()
|
||||
: userContext.apiType === "Cassandra"
|
||||
? this.queryBuilderViewModel().getCqlFilterFromClauses()
|
||||
: this.queryBuilderViewModel().getODataFilterFromClauses();
|
||||
? this.queryBuilderViewModel().getCqlFilterFromClauses(queryTableRows)
|
||||
: this.queryBuilderViewModel().getODataFilterFromClauses(queryTableRows);
|
||||
const filter = queryString;
|
||||
this.queryText(filter);
|
||||
return this.queryText();
|
||||
};
|
||||
|
||||
private setSqlFilter = (): string => {
|
||||
return this.queryBuilderViewModel().getSqlFilterFromClauses();
|
||||
};
|
||||
|
||||
private setCqlFilter = (): string => {
|
||||
return this.queryBuilderViewModel().getCqlFilterFromClauses();
|
||||
private setSqlFilter = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||
return this.queryBuilderViewModel().getSqlFilterFromClauses(queryTableRows);
|
||||
};
|
||||
|
||||
public isHelperEnabled = ko
|
||||
@@ -158,8 +155,9 @@ export default class QueryViewModel {
|
||||
notify: "always",
|
||||
});
|
||||
|
||||
public runQuery = (): DataTables.DataTable => {
|
||||
let filter = this.setFilter();
|
||||
public runQuery = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||
let filter = this.setFilter(queryTableRows);
|
||||
|
||||
if (filter && userContext.apiType !== "Cassandra") {
|
||||
filter = filter.replace(/"/g, "'");
|
||||
}
|
||||
@@ -170,13 +168,15 @@ export default class QueryViewModel {
|
||||
this._tableEntityListViewModel.tableQuery.top = top;
|
||||
this._tableEntityListViewModel.tableQuery.select = select;
|
||||
this._tableEntityListViewModel.oDataQuery(filter);
|
||||
this._tableEntityListViewModel.sqlQuery(this.setSqlFilter());
|
||||
this._tableEntityListViewModel.sqlQuery(this.setSqlFilter(queryTableRows));
|
||||
this._tableEntityListViewModel.cqlQuery(filter);
|
||||
|
||||
return this._tableEntityListViewModel.reloadTable(/*useSetting*/ false, /*resetHeaders*/ false);
|
||||
return userContext.apiType !== "Cassandra"
|
||||
? this._tableEntityListViewModel.sqlQuery()
|
||||
: this._tableEntityListViewModel.cqlQuery();
|
||||
};
|
||||
|
||||
public clearQuery = (): DataTables.DataTable => {
|
||||
public clearQuery = (): void => {
|
||||
this.queryText();
|
||||
this.topValue();
|
||||
this.selectText();
|
||||
@@ -194,16 +194,26 @@ export default class QueryViewModel {
|
||||
this.queryTablesTab.collection.id()
|
||||
)}`
|
||||
);
|
||||
return this._tableEntityListViewModel.reloadTable(false);
|
||||
};
|
||||
|
||||
public selectQueryOptions() {
|
||||
useSidePanel.getState().openSidePanel("Select Column", <TableQuerySelectPanel queryViewModel={this} />);
|
||||
public selectQueryOptions(headers: string[], getSelectMessage: (selectMessage: string) => void): void {
|
||||
this.columnOptions(headers);
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Select Column",
|
||||
<TableQuerySelectPanel queryViewModel={this} headers={headers} getSelectMessage={getSelectMessage} />
|
||||
);
|
||||
}
|
||||
|
||||
public onselectQueryOptionsKeyDown = (source: string, event: KeyboardEvent): boolean => {
|
||||
public onselectQueryOptionsKeyDown = (
|
||||
source: string,
|
||||
event: KeyboardEvent,
|
||||
headers: string[],
|
||||
getSelectMessage: (selectMessage: string) => void
|
||||
): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.selectQueryOptions();
|
||||
this.selectQueryOptions(headers, getSelectMessage);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
<div class="tab-pane tableContainer" data-bind="attr:{id: tabId}" role="tabpanel">
|
||||
<!-- Tables Query Tab Query Builder - Start-->
|
||||
<div
|
||||
class="query-builder"
|
||||
data-bind="with: queryViewModel, attr: {
|
||||
id: queryViewModel.id
|
||||
}"
|
||||
>
|
||||
<!-- Tables Query Tab Errors - Start-->
|
||||
<div class="error-bar">
|
||||
<div class="error-message" aria-label="Error Message" data-bind="visible: hasQueryError">
|
||||
<span><img class="entity-error-Img" src="/error_red.svg" /></span>
|
||||
<span class="error-text" role="alert" data-bind="text: queryErrorMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tables Query Tab Errors - End-->
|
||||
<!-- Tables Query Tab Query Text - Start-->
|
||||
<div class="query-editor-panel" data-bind="visible: isEditorActive">
|
||||
<div>
|
||||
<textarea
|
||||
class="query-editor-text"
|
||||
data-bind="textInput: queryText,
|
||||
css: { 'query-editor-text-invalid': hasQueryError },
|
||||
readOnly: true"
|
||||
name="query-editor"
|
||||
rows="5"
|
||||
cols="100"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tables Query Tab Query Text - End-->
|
||||
<!-- Tables Query Tab Query Helper - Start-->
|
||||
<div data-bind="visible: isHelperActive" style="padding-left: 13px">
|
||||
<div class="clause-table" data-bind="with: queryBuilderViewModel ">
|
||||
<div class="scroll-box scrollable" id="scroll">
|
||||
<table class="clause-table">
|
||||
<thead>
|
||||
<tr class="clause-table-row">
|
||||
<th class="clause-table-cell header-background action-header">
|
||||
<span data-bind="text: actionLabel"></span>
|
||||
</th>
|
||||
<th class="clause-table-cell header-background group-control-header">
|
||||
<button
|
||||
type="button"
|
||||
data-bind="enable: canGroupClauses, attr:{title: groupSelectedClauses}, click: groupClauses"
|
||||
>
|
||||
<img class="and-or-svg" src="/And-Or.svg" alt="Group selected clauses" />
|
||||
</button>
|
||||
</th>
|
||||
<th class="clause-table-cell header-background">
|
||||
<!-- Grouping indicator -->
|
||||
</th>
|
||||
<th class="clause-table-cell header-background and-or-header">
|
||||
<span data-bind="text: andLabel"></span>
|
||||
</th>
|
||||
<th class="clause-table-cell header-background field-header">
|
||||
<span data-bind="text: fieldLabel"></span>
|
||||
</th>
|
||||
<th class="clause-table-cell header-background type-header">
|
||||
<span data-bind="text: dataTypeLabel"></span>
|
||||
</th>
|
||||
<th class="clause-table-cell header-background operator-header">
|
||||
<span data-bind="text: operatorLabel"></span>
|
||||
</th>
|
||||
<th class="clause-table-cell header-background value-header">
|
||||
<span data-bind="text: valueLabel"></span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div
|
||||
class="addClause"
|
||||
role="button"
|
||||
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }, attr: { title: addNewClauseLine }"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="addClause-heading">
|
||||
<span class="clause-table addClause-title">
|
||||
<img
|
||||
class="addclauseProperty-Img"
|
||||
style="margin-bottom: 5px"
|
||||
src="/Add-property.svg"
|
||||
alt="Add new clause"
|
||||
/>
|
||||
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tables Query Tab Query Helper - End-->
|
||||
<!-- Tables Query Tab Advanced Options - Start-->
|
||||
<div class="advanced-options-panel">
|
||||
<div class="advanced-heading">
|
||||
<span
|
||||
class="advanced-title"
|
||||
role="button"
|
||||
data-bind="click:toggleAdvancedOptions, event: { keydown: ontoggleAdvancedOptionsKeyDown }, attr:{ 'aria-expanded': isExpanded() ? 'true' : 'false' }"
|
||||
tabindex="0"
|
||||
>
|
||||
<!-- ko template: { ifnot: isExpanded} -->
|
||||
<div class="themed-images" type="text/html" id="ExpandChevronRight" data-bind="hasFocus: focusExpandIcon">
|
||||
<img class="imgiconwidth expand-triangle expand-triangle-right" src="/Triangle-right.svg" alt="toggle" />
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- ko template: { if: isExpanded} -->
|
||||
<div class="themed-images" type="text/html" id="ExpandChevronDown">
|
||||
<img class="imgiconwidth expand-triangle" src="/Triangle-down.svg" alt="toggle" />
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<span>Advanced Options</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="advanced-options" data-bind="visible: isExpanded">
|
||||
<div class="top">
|
||||
<span>Show top results:</span>
|
||||
<input
|
||||
class="top-input"
|
||||
type="number"
|
||||
data-bind="hasFocus: focusTopResult, textInput: topValue, attr: { title: topValueLimitMessage }"
|
||||
role="textbox"
|
||||
aria-label="Show top results"
|
||||
/>
|
||||
<div role="alert" aria-atomic="true" class="inline-div" data-bind="visible: isExceedingLimit">
|
||||
<img class="advanced-options-icon" src="/QueryBuilder/StatusWarning_16x.png" />
|
||||
<span data-bind="text: topValueLimitMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select">
|
||||
<span> Select fields for query: </span>
|
||||
<div data-bind="visible: isSelected">
|
||||
<img class="advanced-options-icon" src="/QueryBuilder/QueryInformation_16x.png" />
|
||||
<span class="select-options-text" data-bind="text: selectMessage" />
|
||||
</div>
|
||||
<a
|
||||
class="select-options-link"
|
||||
data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
<span>Choose Columns... </span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tables Query Tab Advanced Options - End-->
|
||||
</div>
|
||||
<!-- Tables Query Tab Query Builder - End-->
|
||||
<div
|
||||
class="tablesQueryTab tableContainer"
|
||||
data-bind="with: tableEntityListViewModel, attr: {
|
||||
id: tableEntityListViewModel.id
|
||||
}"
|
||||
>
|
||||
<!-- Keyboard navigation - tabindex is required to make the table focusable. -->
|
||||
<table
|
||||
id="storageTable"
|
||||
class="storage azure-table show-gridlines"
|
||||
tabindex="0"
|
||||
data-bind="tableSource: items, tableSelection: selected"
|
||||
></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script for each clause in the tables query builder -->
|
||||
<script type="text/html" id="queryClause-template">
|
||||
<tr class="clause-table-row">
|
||||
<td class="clause-table-cell action-column">
|
||||
<span
|
||||
class="entity-Add-Cancel"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}"
|
||||
>
|
||||
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
|
||||
</span>
|
||||
<span
|
||||
class="entity-Add-Cancel"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}"
|
||||
>
|
||||
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
|
||||
</span>
|
||||
</td>
|
||||
<td class="clause-table-cell group-control-column">
|
||||
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" />
|
||||
</td>
|
||||
<td>
|
||||
<table class="group-indicator-table">
|
||||
<tbody>
|
||||
<tr
|
||||
data-bind="template: { name: 'groupIndicator-template', foreach: $parent.getClauseGroupViewModels($data), as: 'gi' }"
|
||||
></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td class="clause-table-cell and-or-column">
|
||||
<select
|
||||
class="clause-table-field and-or-column"
|
||||
data-bind="hasFocus: isAndOrFocused, options: $parent.clauseRules, value: and_or, visible: canAnd, attr:{ 'aria-label': and_or }"
|
||||
></select>
|
||||
</td>
|
||||
<td class="clause-table-cell field-column" data-bind="click: $parent.updateColumnOptions">
|
||||
<select
|
||||
class="clause-table-field field-column"
|
||||
data-bind="options: $parent.columnOptions, value: field, attr:{ 'aria-label': field }"
|
||||
></select>
|
||||
</td>
|
||||
<td class="clause-table-cell type-column">
|
||||
<select
|
||||
class="clause-table-field type-column"
|
||||
data-bind="
|
||||
options: $parent.edmTypes,
|
||||
enable: isTypeEditable,
|
||||
value: type,
|
||||
css: {'query-builder-isDisabled': !isTypeEditable()},
|
||||
attr: { 'aria-label': type }"
|
||||
></select>
|
||||
</td>
|
||||
<td class="clause-table-cell operator-column">
|
||||
<select
|
||||
class="clause-table-field operator-column"
|
||||
data-bind="
|
||||
options: $parent.operators,
|
||||
enable: isOperaterEditable,
|
||||
value: operator,
|
||||
css: {'query-builder-isDisabled': !isOperaterEditable()},
|
||||
attr: { 'aria-label': operator }"
|
||||
></select>
|
||||
</td>
|
||||
<td class="clause-table-cell value-column">
|
||||
<!-- ko template: {if: isValue} -->
|
||||
<input
|
||||
type="text"
|
||||
class="clause-table-field value-column"
|
||||
type="search"
|
||||
data-bind="textInput: value, attr: {'aria-label': $parent.valueLabel}"
|
||||
/>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko template: {if: isTimestamp} -->
|
||||
<select
|
||||
class="clause-table-field time-column"
|
||||
data-bind="options: $parent.timeOptions, value: timeValue"
|
||||
></select>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko template: {if: isCustomLastTimestamp} -->
|
||||
<input class="clause-table-field time-column" data-bind="value: customTimeValue, click: customTimestampDialog" />
|
||||
<!-- /ko -->
|
||||
<!-- ko template: {if: isCustomRangeTimestamp} -->
|
||||
<input class="clause-table-field time-column" type="datetime-local" step="1" data-bind="value: customTimeValue" />
|
||||
<!-- /ko -->
|
||||
</td>
|
||||
</tr>
|
||||
</script>
|
||||
|
||||
<!-- Script for each clause group in the tables query builder -->
|
||||
<script type="text/html" id="groupIndicator-template">
|
||||
<td
|
||||
class="group-indicator-column"
|
||||
data-bind="style: {backgroundColor: gi.backgroundColor, borderTop: gi.showTopBorder.peek() ? 'solid thin #CCCCCC' : 'none', borderLeft: gi.showLeftBorder.peek() ? 'solid thin #CCCCCC' : 'none', borderBottom: gi.showBottomBorder.peek() ? 'solid thin #CCCCCC' : gi.borderBackgroundColor}"
|
||||
>
|
||||
<!-- ko template: {if: gi.canUngroup} -->
|
||||
<button type="button" data-bind="click: ungroupClauses, attr: {title: ungroupClausesLabel}">
|
||||
<img src="/QueryBuilder/UngroupClause_16x.png" alt="Ungroup clauses" />
|
||||
</button>
|
||||
<!-- /ko -->
|
||||
</td>
|
||||
</script>
|
||||
@@ -1,285 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import AddEntityIcon from "../../../images/AddEntity.svg";
|
||||
import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg";
|
||||
import EditEntityIcon from "../../../images/Edit-entity.svg";
|
||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||
import QueryBuilderIcon from "../../../images/Query-Builder.svg";
|
||||
import QueryTextIcon from "../../../images/Query-Text.svg";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { AddTableEntityPanel } from "../Panes/Tables/AddTableEntityPanel";
|
||||
import { EditTableEntityPanel } from "../Panes/Tables/EditTableEntityPanel";
|
||||
import TableCommands from "../Tables/DataTable/TableCommands";
|
||||
import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel";
|
||||
import QueryViewModel from "../Tables/QueryBuilder/QueryViewModel";
|
||||
import { CassandraAPIDataClient, TableDataClient } from "../Tables/TableDataClient";
|
||||
import template from "./QueryTablesTab.html";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
// Will act as table explorer class
|
||||
export default class QueryTablesTab extends TabsBase {
|
||||
public readonly html = template;
|
||||
public collection: ViewModels.Collection;
|
||||
public tableEntityListViewModel = ko.observable<TableEntityListViewModel>();
|
||||
public queryViewModel = ko.observable<QueryViewModel>();
|
||||
public tableCommands: TableCommands;
|
||||
public tableDataClient: TableDataClient;
|
||||
|
||||
public queryText = ko.observable("PartitionKey eq 'partitionKey1'"); // Start out with an example they can modify
|
||||
public selectedQueryText = ko.observable("").extend({ notify: "always" });
|
||||
|
||||
public executeQueryButton: ViewModels.Button;
|
||||
public addEntityButton: ViewModels.Button;
|
||||
public editEntityButton: ViewModels.Button;
|
||||
public deleteEntityButton: ViewModels.Button;
|
||||
public queryBuilderButton: ViewModels.Button;
|
||||
public queryTextButton: ViewModels.Button;
|
||||
public container: Explorer;
|
||||
|
||||
constructor(options: ViewModels.TabOptions) {
|
||||
super(options);
|
||||
|
||||
this.container = options.collection && options.collection.container;
|
||||
this.tableCommands = new TableCommands(this.container);
|
||||
this.tableDataClient = this.container.tableDataClient;
|
||||
this.tableEntityListViewModel(new TableEntityListViewModel(this.tableCommands, this));
|
||||
this.tableEntityListViewModel().queryTablesTab = this;
|
||||
this.queryViewModel(new QueryViewModel(this));
|
||||
const sampleQuerySubscription = this.tableEntityListViewModel().items.subscribe(() => {
|
||||
if (this.tableEntityListViewModel().items().length > 0 && userContext.apiType === "Tables") {
|
||||
this.queryViewModel().queryBuilderViewModel().setExample();
|
||||
}
|
||||
sampleQuerySubscription.dispose();
|
||||
});
|
||||
|
||||
this.executeQueryButton = {
|
||||
enabled: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
|
||||
visible: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
};
|
||||
|
||||
this.queryBuilderButton = {
|
||||
enabled: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
|
||||
visible: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
|
||||
isSelected: ko.computed<boolean>(() => {
|
||||
return this.queryViewModel() ? this.queryViewModel().isHelperActive() : false;
|
||||
}),
|
||||
};
|
||||
|
||||
this.queryTextButton = {
|
||||
enabled: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
|
||||
visible: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
|
||||
isSelected: ko.computed<boolean>(() => {
|
||||
return this.queryViewModel() ? this.queryViewModel().isEditorActive() : false;
|
||||
}),
|
||||
};
|
||||
|
||||
this.addEntityButton = {
|
||||
enabled: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
|
||||
visible: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
};
|
||||
|
||||
this.editEntityButton = {
|
||||
enabled: ko.computed<boolean>(() => {
|
||||
return this.tableCommands.isEnabled(
|
||||
TableCommands.editEntityCommand,
|
||||
this.tableEntityListViewModel().selected()
|
||||
);
|
||||
}),
|
||||
|
||||
visible: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
};
|
||||
|
||||
this.deleteEntityButton = {
|
||||
enabled: ko.computed<boolean>(() => {
|
||||
return this.tableCommands.isEnabled(
|
||||
TableCommands.deleteEntitiesCommand,
|
||||
this.tableEntityListViewModel().selected()
|
||||
);
|
||||
}),
|
||||
|
||||
visible: ko.computed<boolean>(() => {
|
||||
return true;
|
||||
}),
|
||||
};
|
||||
|
||||
this.buildCommandBarOptions();
|
||||
}
|
||||
|
||||
public onAddEntityClick = (): void => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Add Table Row",
|
||||
<AddTableEntityPanel
|
||||
tableDataClient={this.tableDataClient}
|
||||
queryTablesTab={this}
|
||||
tableEntityListViewModel={this.tableEntityListViewModel()}
|
||||
cassandraApiClient={new CassandraAPIDataClient()}
|
||||
/>,
|
||||
"700px"
|
||||
);
|
||||
};
|
||||
|
||||
public onEditEntityClick = (): void => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Edit Table Entity",
|
||||
<EditTableEntityPanel
|
||||
tableDataClient={this.tableDataClient}
|
||||
queryTablesTab={this}
|
||||
tableEntityListViewModel={this.tableEntityListViewModel()}
|
||||
cassandraApiClient={new CassandraAPIDataClient()}
|
||||
/>,
|
||||
"700px"
|
||||
);
|
||||
};
|
||||
|
||||
public onDeleteEntityClick = (): void => {
|
||||
this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel());
|
||||
};
|
||||
|
||||
public onActivate(): void {
|
||||
super.onActivate();
|
||||
const columns =
|
||||
!!this.tableEntityListViewModel() &&
|
||||
!!this.tableEntityListViewModel().table &&
|
||||
this.tableEntityListViewModel().table.columns;
|
||||
if (columns) {
|
||||
columns.adjust();
|
||||
$(window).resize();
|
||||
}
|
||||
}
|
||||
|
||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
if (this.queryBuilderButton.visible()) {
|
||||
const label = userContext.apiType === "Cassandra" ? "CQL Query Builder" : "Query Builder";
|
||||
buttons.push({
|
||||
iconSrc: QueryBuilderIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => this.queryViewModel().selectHelper(),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: !this.queryBuilderButton.enabled(),
|
||||
isSelected: this.queryBuilderButton.isSelected(),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.queryTextButton.visible()) {
|
||||
const label = userContext.apiType === "Cassandra" ? "CQL Query Text" : "Query Text";
|
||||
buttons.push({
|
||||
iconSrc: QueryTextIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => this.queryViewModel().selectEditor(),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: !this.queryTextButton.enabled(),
|
||||
isSelected: this.queryTextButton.isSelected(),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.executeQueryButton.visible()) {
|
||||
const label = "Run Query";
|
||||
buttons.push({
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => this.queryViewModel().runQuery(),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: !this.executeQueryButton.enabled(),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addEntityButton.visible()) {
|
||||
const label = userContext.apiType === "Cassandra" ? "Add Row" : "Add Entity";
|
||||
buttons.push({
|
||||
iconSrc: AddEntityIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: this.onAddEntityClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: !this.addEntityButton.enabled(),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.editEntityButton.visible()) {
|
||||
const label = userContext.apiType === "Cassandra" ? "Edit Row" : "Edit Entity";
|
||||
buttons.push({
|
||||
iconSrc: EditEntityIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: this.onEditEntityClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: !this.editEntityButton.enabled(),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.deleteEntityButton.visible()) {
|
||||
const label = userContext.apiType === "Cassandra" ? "Delete Rows" : "Delete Entities";
|
||||
buttons.push({
|
||||
iconSrc: DeleteEntitiesIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: this.onDeleteEntityClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: !this.deleteEntityButton.enabled(),
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
protected buildCommandBarOptions(): void {
|
||||
ko.computed(() =>
|
||||
ko.toJSON([
|
||||
this.queryBuilderButton.visible,
|
||||
this.queryBuilderButton.enabled,
|
||||
this.queryTextButton.visible,
|
||||
this.queryTextButton.enabled,
|
||||
this.executeQueryButton.visible,
|
||||
this.executeQueryButton.enabled,
|
||||
this.addEntityButton.visible,
|
||||
this.addEntityButton.enabled,
|
||||
this.editEntityButton.visible,
|
||||
this.editEntityButton.enabled,
|
||||
this.deleteEntityButton.visible,
|
||||
this.deleteEntityButton.enabled,
|
||||
])
|
||||
).subscribe(() => this.updateNavbarWithTabsButtons());
|
||||
this.updateNavbarWithTabsButtons();
|
||||
}
|
||||
}
|
||||
169
src/Explorer/Tabs/QueryTablesTab/QueryTableEntityClause.tsx
Normal file
169
src/Explorer/Tabs/QueryTablesTab/QueryTableEntityClause.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import {
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
IDropdownOption,
|
||||
IDropdownStyles,
|
||||
IImageProps,
|
||||
Image,
|
||||
IStackTokens,
|
||||
Stack,
|
||||
TextField,
|
||||
TooltipHost,
|
||||
} from "@fluentui/react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import AddIcon from "../../../../images/Add-property.svg";
|
||||
import CancelIcon from "../../../../images/Entity_cancel.svg";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { IOption } from "./QueryTableTabUtils";
|
||||
const dropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 100 } };
|
||||
|
||||
export interface IQueryTableEntityClauseProps {
|
||||
index: number;
|
||||
entityValue: string;
|
||||
entityValuePlaceHolder?: string;
|
||||
selectedOperator: string;
|
||||
selectedOperation: string;
|
||||
operatorOptions: IOption[];
|
||||
operationOptions: IOption[];
|
||||
isQueryTableEntityChecked: boolean;
|
||||
selectedField: string;
|
||||
fieldOptions: IOption[];
|
||||
entityTypeOptions: IOption[];
|
||||
selectedEntityType: string;
|
||||
isTimeStampSelected?: boolean;
|
||||
selectedTimestamp: string;
|
||||
timestampOptions: IOption[];
|
||||
onAddNewClause?: () => void;
|
||||
onDeleteClause?: () => void;
|
||||
onQueryTableEntityCheck: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
|
||||
onDropdownChange: (selectedOption: IDropdownOption, selectedOptionType: string) => void;
|
||||
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
|
||||
onAddNewClauseKeyDown?: (ev: React.KeyboardEvent<HTMLImageElement>) => void;
|
||||
onDeleteCaluseKeyDown?: (ev: React.KeyboardEvent<HTMLImageElement>) => void;
|
||||
}
|
||||
|
||||
export const QueryTableEntityClause: FunctionComponent<IQueryTableEntityClauseProps> = ({
|
||||
index,
|
||||
entityValue,
|
||||
entityValuePlaceHolder,
|
||||
selectedOperator,
|
||||
operatorOptions,
|
||||
selectedField,
|
||||
isQueryTableEntityChecked,
|
||||
fieldOptions,
|
||||
entityTypeOptions,
|
||||
selectedEntityType,
|
||||
selectedOperation,
|
||||
operationOptions,
|
||||
isTimeStampSelected,
|
||||
selectedTimestamp,
|
||||
timestampOptions,
|
||||
onQueryTableEntityCheck,
|
||||
onAddNewClause,
|
||||
onDeleteClause,
|
||||
onDropdownChange,
|
||||
onEntityValueChange,
|
||||
onAddNewClauseKeyDown,
|
||||
onDeleteCaluseKeyDown,
|
||||
}: IQueryTableEntityClauseProps): JSX.Element => {
|
||||
const cancelImageProps: IImageProps = {
|
||||
className: "querybuilder-cancelImg",
|
||||
};
|
||||
|
||||
const addImageProps: IImageProps = {
|
||||
className: "querybuilder-addpropertyImg",
|
||||
};
|
||||
|
||||
const sectionStackTokens: IStackTokens = { childrenGap: 12 };
|
||||
|
||||
const validateEntityTypeOption = (): boolean => {
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
return true;
|
||||
} else if (selectedField === "PartitionKey" || selectedField === "RowKey" || selectedField === "Timestamp") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack horizontal tokens={sectionStackTokens}>
|
||||
<TooltipHost content="Add new clause" id="addNewClause">
|
||||
<Image
|
||||
{...addImageProps}
|
||||
src={AddIcon}
|
||||
alt="Add new clause"
|
||||
id="addNewClause"
|
||||
onClick={onAddNewClause}
|
||||
onKeyDown={onAddNewClauseKeyDown}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</TooltipHost>
|
||||
<TooltipHost content="Delete clause" id="deleteClause">
|
||||
<Image
|
||||
{...cancelImageProps}
|
||||
src={CancelIcon}
|
||||
alt="delete clause"
|
||||
id="deleteClause"
|
||||
onClick={onDeleteClause}
|
||||
onKeyDown={onDeleteCaluseKeyDown}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</TooltipHost>
|
||||
<Checkbox checked={isQueryTableEntityChecked} onChange={onQueryTableEntityCheck} />
|
||||
<Dropdown
|
||||
style={{ visibility: index > 0 ? "visible" : "hidden" }}
|
||||
selectedKey={selectedOperation}
|
||||
onChange={(_event: React.FormEvent<HTMLElement>, selectedOption: IDropdownOption) =>
|
||||
onDropdownChange(selectedOption, "selectedOperation")
|
||||
}
|
||||
options={operationOptions}
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
<Dropdown
|
||||
selectedKey={selectedField}
|
||||
onChange={(_event: React.FormEvent<HTMLElement>, selectedOption: IDropdownOption) =>
|
||||
onDropdownChange(selectedOption, "selectedField")
|
||||
}
|
||||
options={fieldOptions}
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
<Dropdown
|
||||
selectedKey={selectedEntityType}
|
||||
onChange={(_event: React.FormEvent<HTMLElement>, selectedOption: IDropdownOption) =>
|
||||
onDropdownChange(selectedOption, "selectedEntityType")
|
||||
}
|
||||
options={entityTypeOptions}
|
||||
disabled={validateEntityTypeOption()}
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
<Dropdown
|
||||
selectedKey={selectedOperator}
|
||||
onChange={(_event: React.FormEvent<HTMLElement>, selectedOption: IDropdownOption) =>
|
||||
onDropdownChange(selectedOption, "selectedOperator")
|
||||
}
|
||||
options={operatorOptions}
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
{isTimeStampSelected ? (
|
||||
<Dropdown
|
||||
selectedKey={selectedTimestamp}
|
||||
onChange={(_event: React.FormEvent<HTMLElement>, selectedOption: IDropdownOption) =>
|
||||
onDropdownChange(selectedOption, "selectedTimestamp")
|
||||
}
|
||||
options={timestampOptions}
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
) : (
|
||||
<TextField
|
||||
autoFocus
|
||||
placeholder={entityValuePlaceHolder}
|
||||
value={entityValue}
|
||||
onChange={onEntityValueChange}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
104
src/Explorer/Tabs/QueryTablesTab/QueryTableTabUtils.tsx
Normal file
104
src/Explorer/Tabs/QueryTablesTab/QueryTableTabUtils.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { IColumn, Selection } from "@fluentui/react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import Explorer from "../../Explorer";
|
||||
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||
import * as Entities from "../../Tables/Entities";
|
||||
import ClauseGroup from "../../Tables/QueryBuilder/ClauseGroup";
|
||||
import QueryViewModel from "../../Tables/QueryBuilder/QueryViewModel";
|
||||
import TabsBase from "../TabsBase";
|
||||
import NewQueryTablesTab from "./QueryTablesTab";
|
||||
export interface Button {
|
||||
visible: boolean;
|
||||
enabled: boolean;
|
||||
isSelected?: boolean;
|
||||
}
|
||||
|
||||
export interface IOption {
|
||||
key: string;
|
||||
text: string;
|
||||
}
|
||||
export interface IDocument {
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
timeStamp: string;
|
||||
}
|
||||
export interface IQueryTablesTabComponentProps {
|
||||
tabKind: ViewModels.CollectionTabKind;
|
||||
title: string;
|
||||
tabPath: string;
|
||||
collection: ViewModels.CollectionBase;
|
||||
node: ViewModels.TreeNode;
|
||||
onLoadStartKey: number;
|
||||
container: Explorer;
|
||||
tabsBaseInstance: TabsBase;
|
||||
queryTablesTab: NewQueryTablesTab;
|
||||
}
|
||||
|
||||
export interface IQueryTablesTabComponentStates {
|
||||
tableEntityListViewModel: TableEntityListViewModel;
|
||||
queryViewModel: QueryViewModel;
|
||||
queryText: string;
|
||||
selectedQueryText: string;
|
||||
executeQueryButton: Button;
|
||||
queryBuilderButton: Button;
|
||||
queryTextButton: Button;
|
||||
addEntityButton: Button;
|
||||
editEntityButton: Button;
|
||||
deleteEntityButton: Button;
|
||||
isHelperActive: boolean;
|
||||
columns: IColumn[];
|
||||
items: IDocument[];
|
||||
isExpanded: boolean;
|
||||
isEditorActive: boolean;
|
||||
selectedItems: Entities.ITableEntity[];
|
||||
isValue: boolean;
|
||||
isTimestamp: boolean;
|
||||
isCustomLastTimestamp: boolean;
|
||||
isCustomRangeTimestamp: boolean;
|
||||
operators: string[];
|
||||
selectMessage: string;
|
||||
queryTableRows: IQueryTableRowsType[];
|
||||
originalItems: IDocument[];
|
||||
rowSelected: boolean;
|
||||
selection: Selection;
|
||||
entities: Entities.ITableEntity[];
|
||||
headers: string[];
|
||||
isLoading: boolean;
|
||||
queryErrorMessage: string;
|
||||
hasQueryError: boolean;
|
||||
currentPage: number;
|
||||
currentStartIndex: number;
|
||||
fromDocument: number;
|
||||
toDocument: number;
|
||||
selectedItem: number;
|
||||
}
|
||||
|
||||
export interface IQueryTableRowsType {
|
||||
isQueryTableEntityChecked: boolean;
|
||||
isTimeStampSelected: boolean;
|
||||
selectedOperator: string;
|
||||
selectedField: string;
|
||||
entityValue: string;
|
||||
selectedEntityType: string;
|
||||
selectedOperation: string;
|
||||
selectedTimestamp: string;
|
||||
fieldOptions: IOption[];
|
||||
operatorOptions: IOption[];
|
||||
entityTypeOptions: IOption[];
|
||||
operationOptions: IOption[];
|
||||
timestampOptions: IOption[];
|
||||
id: string;
|
||||
clauseGroup: ClauseGroup;
|
||||
isLocal: boolean;
|
||||
isTimestamp: boolean;
|
||||
isValue: boolean;
|
||||
isCustomRangeTimestamp: boolean;
|
||||
customTimeValue: string;
|
||||
timeValue: string;
|
||||
}
|
||||
|
||||
export const getformattedOptions = (options: Array<string>): IOption[] => {
|
||||
return options.map((option) => {
|
||||
return { key: option, text: option };
|
||||
});
|
||||
};
|
||||
43
src/Explorer/Tabs/QueryTablesTab/QueryTablesTab.tsx
Normal file
43
src/Explorer/Tabs/QueryTablesTab/QueryTablesTab.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import Explorer from "../../Explorer";
|
||||
import TableCommands from "../../Tables/DataTable/TableCommands";
|
||||
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||
import TabsBase from "../TabsBase";
|
||||
import QueryTablesTabComponent from "./QueryTablesTabComponent";
|
||||
import { IQueryTablesTabComponentProps } from "./QueryTableTabUtils";
|
||||
|
||||
interface QueryTablesTabProps {
|
||||
container: Explorer;
|
||||
}
|
||||
|
||||
class NewQueryTablesTab extends TabsBase {
|
||||
public iQueryTablesTabCompProps: IQueryTablesTabComponentProps;
|
||||
public collection: ViewModels.Collection;
|
||||
public tableEntityListViewModel: TableEntityListViewModel;
|
||||
public tableCommands: TableCommands;
|
||||
public container: Explorer;
|
||||
|
||||
constructor(options: ViewModels.TabOptions, props: QueryTablesTabProps) {
|
||||
super(options);
|
||||
this.container = props.container;
|
||||
this.tableCommands = new TableCommands(props.container);
|
||||
this.tableEntityListViewModel = new TableEntityListViewModel(this.tableCommands, this);
|
||||
this.iQueryTablesTabCompProps = {
|
||||
tabKind: options.tabKind,
|
||||
title: options.title,
|
||||
tabPath: options.tabPath,
|
||||
collection: options.collection,
|
||||
node: options.node,
|
||||
onLoadStartKey: options.onLoadStartKey,
|
||||
container: props.container,
|
||||
tabsBaseInstance: this,
|
||||
queryTablesTab: this,
|
||||
};
|
||||
}
|
||||
public render(): JSX.Element {
|
||||
return <QueryTablesTabComponent {...this.iQueryTablesTabCompProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default NewQueryTablesTab;
|
||||
1150
src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx
Normal file
1150
src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user