mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
51 Commits
eslint/fix
...
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 |
@@ -80,10 +80,19 @@ 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/TableEntityListViewModel.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
|
||||
src/Explorer/Tabs/ConflictsTab.ts
|
||||
@@ -106,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
|
||||
@@ -129,13 +143,20 @@ 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/GraphVizComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||
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/VirtualCommandBarComponent.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/contents/index.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 = {
|
||||
|
||||
@@ -339,11 +339,9 @@ export enum ConflictOperationType {
|
||||
}
|
||||
|
||||
export enum ConnectionStatusType {
|
||||
Connect = "Connect",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Failed = "Connection Failed",
|
||||
ReConnect = "Reconnect",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
@@ -355,32 +353,15 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
||||
|
||||
export class Notebook {
|
||||
public static readonly defaultBasePath = "./notebooks";
|
||||
public static readonly heartbeatDelayMs = 60000;
|
||||
public static readonly heartbeatDelayMs = 5000;
|
||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||
public static readonly autoSaveIntervalMs = 120000;
|
||||
public static readonly memoryGuageToGB = 1048576;
|
||||
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.";
|
||||
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
|
||||
public static 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.";
|
||||
public static newNotebookModalTitle = "Create Notebook in temporary workspace";
|
||||
public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
|
||||
public static 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.";
|
||||
public static 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. ";
|
||||
public static 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.";
|
||||
public static 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. ";
|
||||
public static cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
|
||||
public static cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
|
||||
public static learnMore = "Learn more.";
|
||||
}
|
||||
|
||||
export class SparkLibrary {
|
||||
|
||||
@@ -54,7 +54,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
<TextField
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
className="addEntityTextField"
|
||||
id="entityValueId"
|
||||
// id="entityValueId"
|
||||
autoFocus
|
||||
disabled={isEntityValueDisable}
|
||||
type={entityValueType}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
|
||||
className={className}
|
||||
as="tr"
|
||||
aria-label={node.caption}
|
||||
onActivated={() => this.props.onRootNodeSelected(node.id)}
|
||||
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
|
||||
key={node.id}
|
||||
>
|
||||
<td className="resultItem">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import * as Q from "q";
|
||||
import React from "react";
|
||||
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
|
||||
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
|
||||
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
||||
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
||||
|
||||
describe("Property pane", () => {
|
||||
const title = "My Title";
|
||||
@@ -37,18 +37,17 @@ describe("Property pane", () => {
|
||||
return {
|
||||
expandedTitle: title,
|
||||
isCollapsed: false,
|
||||
onCollapsedChanged: jest.fn(),
|
||||
onCollapsedChanged: (newValue: boolean): void => {},
|
||||
node: highlightedNode,
|
||||
getPkIdFromNodeData: (): string => undefined,
|
||||
collectionPartitionKeyProperty: undefined,
|
||||
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
|
||||
selectNode: jest.fn(),
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
|
||||
possibleEdgeLabels: undefined,
|
||||
//eslint-disable-next-line
|
||||
editGraphEdges: (): Q.Promise<any> => Q.resolve(),
|
||||
deleteHighlightedNode: jest.fn(),
|
||||
onModeChanged: jest.fn(),
|
||||
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
||||
collectionPartitionKeyProperty: null,
|
||||
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
||||
selectNode: (id: string): void => {},
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
||||
possibleEdgeLabels: null,
|
||||
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
||||
deleteHighlightedNode: (): void => {},
|
||||
onModeChanged: (newMode: Mode): void => {},
|
||||
viewMode: Mode.READONLY_PROP,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
|
||||
super(props);
|
||||
this.state = {
|
||||
editedProperties: {
|
||||
pkId: undefined,
|
||||
pkId: null,
|
||||
readOnlyProperties: [],
|
||||
existingProperties: [],
|
||||
addedProperties: [],
|
||||
@@ -98,12 +98,15 @@ export class NodePropertiesComponent extends React.Component<
|
||||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
|
||||
public static getDerivedStateFromProps(
|
||||
props: NodePropertiesComponentProps,
|
||||
state: NodePropertiesComponentState
|
||||
): Partial<NodePropertiesComponentState> {
|
||||
if (props.viewMode !== Mode.READONLY_PROP) {
|
||||
return { isDeleteConfirm: false };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
@@ -135,10 +138,10 @@ export class NodePropertiesComponent extends React.Component<
|
||||
* @param value
|
||||
*/
|
||||
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
||||
if (value === undefined) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
const type = typeof value;
|
||||
let type = typeof value;
|
||||
switch (type) {
|
||||
case "number":
|
||||
case "boolean":
|
||||
@@ -169,9 +172,10 @@ export class NodePropertiesComponent extends React.Component<
|
||||
];
|
||||
|
||||
const existingProps: ViewModels.InputProperty[] = [];
|
||||
|
||||
if (this.props.node.hasOwnProperty("properties")) {
|
||||
const hProps = this.props.node["properties"];
|
||||
for (const p in hProps) {
|
||||
for (let p in hProps) {
|
||||
const propValues = hProps[p];
|
||||
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
||||
key: p,
|
||||
@@ -433,7 +437,7 @@ export class NodePropertiesComponent extends React.Component<
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -596,7 +596,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
||||
return {
|
||||
iconSrc: GitHubIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
@@ -606,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 }),
|
||||
}));
|
||||
|
||||
@@ -4,8 +4,6 @@ import * as React from "react";
|
||||
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
||||
|
||||
export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
|
||||
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
|
||||
const result = useFullScreenURLs();
|
||||
if (!result) {
|
||||
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
|
||||
@@ -27,9 +25,8 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
<DefaultButton
|
||||
onClick={() => {
|
||||
copyToClipboard(readWriteUrl);
|
||||
setIsReadWriteUrlCopy(true);
|
||||
}}
|
||||
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
|
||||
text="Copy"
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
/>
|
||||
<PrimaryButton
|
||||
@@ -44,10 +41,9 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||
<DefaultButton
|
||||
onClick={() => {
|
||||
setIsReadUrlCopy(true);
|
||||
copyToClipboard(readUrl);
|
||||
}}
|
||||
text={isReadUrlCopy ? "Copied" : "Copy"}
|
||||
text="Copy"
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
/>
|
||||
<PrimaryButton
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -23,10 +23,12 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
|
||||
|
||||
export interface AddDatabasePaneProps {
|
||||
explorer: Explorer;
|
||||
buttonElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
explorer: container,
|
||||
buttonElement,
|
||||
}: AddDatabasePaneProps) => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
let throughput: number;
|
||||
@@ -77,6 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
};
|
||||
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||
buttonElement.focus();
|
||||
}, []);
|
||||
|
||||
const onSubmit = () => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -307,16 +307,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
title: "New " + getDatabaseName(),
|
||||
description: undefined,
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
|
||||
onClick: () => this.openAddDatabasePanel(),
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private openAddDatabasePanel() {
|
||||
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"New " + getDatabaseName(),
|
||||
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
|
||||
);
|
||||
}
|
||||
|
||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||
return {
|
||||
iconSrc: NotebookIcon,
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
@@ -32,6 +31,8 @@ export interface CassandraTableKey {
|
||||
}
|
||||
|
||||
export abstract class TableDataClient {
|
||||
constructor() {}
|
||||
|
||||
public abstract createDocument(
|
||||
collection: ViewModels.Collection,
|
||||
entity: Entities.ITableEntity
|
||||
@@ -53,7 +54,7 @@ export abstract class TableDataClient {
|
||||
public abstract deleteDocuments(
|
||||
collection: ViewModels.Collection,
|
||||
entitiesToDelete: Entities.ITableEntity[]
|
||||
): Promise<unknown>;
|
||||
): Promise<any>;
|
||||
}
|
||||
|
||||
export class TablesAPIDataClient extends TableDataClient {
|
||||
@@ -66,7 +67,7 @@ export class TablesAPIDataClient extends TableDataClient {
|
||||
collection,
|
||||
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
|
||||
).then(
|
||||
(newDocument: unknown) => {
|
||||
(newDocument: any) => {
|
||||
const newEntity = TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
|
||||
deferred.resolve(newEntity);
|
||||
},
|
||||
@@ -145,7 +146,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
const clearInProgressMessage = logConsoleProgress(`Adding new row to table ${collection.id()}`);
|
||||
let properties = "(";
|
||||
let values = "(";
|
||||
for (const property in entity) {
|
||||
for (let property in entity) {
|
||||
if (entity[property]._ === null) {
|
||||
continue;
|
||||
}
|
||||
@@ -163,7 +164,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
const deferred = Q.defer<Entities.ITableEntity>();
|
||||
this.queryDocuments(collection, query)
|
||||
.then(
|
||||
() => {
|
||||
(data: any) => {
|
||||
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
|
||||
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
|
||||
logConsoleInfo(`Successfully added new row to table ${collection.id()}`);
|
||||
@@ -187,10 +188,10 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
|
||||
try {
|
||||
let whereSegment = " WHERE";
|
||||
const keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
|
||||
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
|
||||
collection.cassandraKeys.clusteringKeys
|
||||
);
|
||||
for (const keyIndex in keys) {
|
||||
for (let keyIndex in keys) {
|
||||
const key = keys[keyIndex].property;
|
||||
const keyType = keys[keyIndex].type;
|
||||
whereSegment += this.isStringType(keyType)
|
||||
@@ -201,21 +202,14 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
|
||||
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
||||
let isPropertyUpdated = false;
|
||||
let isFirstPropertyToUpdate = true;
|
||||
for (const property in newEntity) {
|
||||
for (let property in newEntity) {
|
||||
if (
|
||||
!originalDocument[property] ||
|
||||
newEntity[property]._.toString() !== originalDocument[property]._.toString()
|
||||
) {
|
||||
let propertyQuerySegment = this.isStringType(newEntity[property].$)
|
||||
? `${property} = '${newEntity[property]._}',`
|
||||
: `${property} = ${newEntity[property]._},`;
|
||||
// Only add the "SET" keyword once
|
||||
if (isFirstPropertyToUpdate) {
|
||||
propertyQuerySegment = " SET " + propertyQuerySegment;
|
||||
isFirstPropertyToUpdate = false;
|
||||
}
|
||||
updateQuery += propertyQuerySegment;
|
||||
updateQuery += this.isStringType(newEntity[property].$)
|
||||
? ` SET ${property} = '${newEntity[property]._}',`
|
||||
: ` SET ${property} = ${newEntity[property]._},`;
|
||||
isPropertyUpdated = true;
|
||||
}
|
||||
}
|
||||
@@ -228,7 +222,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
|
||||
let deleteQuery = `DELETE `;
|
||||
let isPropertyDeleted = false;
|
||||
for (const property in originalDocument) {
|
||||
for (let property in originalDocument) {
|
||||
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
|
||||
deleteQuery += ` ${property},`;
|
||||
isPropertyDeleted = true;
|
||||
@@ -332,16 +326,16 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
resourceId: string,
|
||||
explorer: Explorer,
|
||||
createKeyspaceQuery: string
|
||||
): Q.Promise<unknown> {
|
||||
): Q.Promise<any> {
|
||||
if (!createKeyspaceQuery) {
|
||||
return Q.reject("No query specified");
|
||||
}
|
||||
|
||||
const deferred: Q.Deferred<unknown> = Q.defer();
|
||||
const deferred: Q.Deferred<any> = Q.defer();
|
||||
const clearInProgressMessage = logConsoleProgress(`Creating a new keyspace with query ${createKeyspaceQuery}`);
|
||||
this.createOrDeleteQuery(cassandraEndpoint, resourceId, createKeyspaceQuery)
|
||||
.then(
|
||||
() => {
|
||||
(data: any) => {
|
||||
logConsoleInfo(`Successfully created a keyspace with query ${createKeyspaceQuery}`);
|
||||
deferred.resolve();
|
||||
},
|
||||
@@ -365,8 +359,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
explorer: Explorer,
|
||||
createTableQuery: string,
|
||||
createKeyspaceQuery?: string
|
||||
): Q.Promise<unknown> {
|
||||
let createKeyspacePromise: Q.Promise<unknown>;
|
||||
): Q.Promise<any> {
|
||||
let createKeyspacePromise: Q.Promise<any>;
|
||||
if (createKeyspaceQuery) {
|
||||
createKeyspacePromise = this.createKeyspace(cassandraEndpoint, resourceId, explorer, createKeyspaceQuery);
|
||||
} else {
|
||||
@@ -379,7 +373,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
const clearInProgressMessage = logConsoleProgress(`Creating a new table with query ${createTableQuery}`);
|
||||
this.createOrDeleteQuery(cassandraEndpoint, resourceId, createTableQuery)
|
||||
.then(
|
||||
() => {
|
||||
(data: any) => {
|
||||
logConsoleInfo(`Successfully created a table with query ${createTableQuery}`);
|
||||
deferred.resolve();
|
||||
},
|
||||
@@ -398,7 +392,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
}
|
||||
|
||||
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
||||
if (collection.cassandraKeys) {
|
||||
if (!!collection.cassandraKeys) {
|
||||
return Q.resolve(collection.cassandraKeys);
|
||||
}
|
||||
const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`);
|
||||
@@ -407,7 +401,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
authType === AuthType.EncryptedToken
|
||||
? Constants.CassandraBackend.guestKeysApi
|
||||
: Constants.CassandraBackend.keysApi;
|
||||
const endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
|
||||
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
|
||||
const deferred = Q.defer<CassandraTableKeys>();
|
||||
|
||||
$.ajax(endpoint, {
|
||||
@@ -428,7 +422,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`);
|
||||
deferred.resolve(data);
|
||||
},
|
||||
(error: Error) => {
|
||||
(error: any) => {
|
||||
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
@@ -438,7 +432,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
}
|
||||
|
||||
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
||||
if (collection.cassandraSchema) {
|
||||
if (!!collection.cassandraSchema) {
|
||||
return Q.resolve(collection.cassandraSchema);
|
||||
}
|
||||
const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`);
|
||||
@@ -447,7 +441,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
authType === AuthType.EncryptedToken
|
||||
? Constants.CassandraBackend.guestSchemaApi
|
||||
: Constants.CassandraBackend.schemaApi;
|
||||
const endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
|
||||
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
|
||||
const deferred = Q.defer<CassandraTableKey[]>();
|
||||
|
||||
$.ajax(endpoint, {
|
||||
@@ -468,7 +462,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`);
|
||||
deferred.resolve(data.columns);
|
||||
},
|
||||
(error: Error) => {
|
||||
(error: any) => {
|
||||
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
@@ -495,7 +489,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
beforeSend: this.setAuthorizationHeader,
|
||||
cache: false,
|
||||
}).then(
|
||||
() => {
|
||||
(data: any) => {
|
||||
deferred.resolve();
|
||||
},
|
||||
(reason) => {
|
||||
|
||||
@@ -118,7 +118,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
||||
const saveButtonChildren = [];
|
||||
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
saveButtonChildren.push({
|
||||
iconName: copyToLabel,
|
||||
iconName: "Copy",
|
||||
onCommandClick: () => this.copyNotebook(),
|
||||
commandButtonLabel: copyToLabel,
|
||||
hasPopup: 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
@@ -32,7 +32,8 @@ import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
|
||||
import { NewMongoQueryTab } from "../Tabs/MongoQueryTab/MongoQueryTab";
|
||||
import { NewMongoShellTab } from "../Tabs/MongoShellTab/MongoShellTab";
|
||||
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
||||
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
||||
// import QueryTablesTab from "../Tabs/QueryTablesTab";
|
||||
import NewQueryTablesTab from "../Tabs/QueryTablesTab/QueryTablesTab";
|
||||
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
@@ -391,13 +392,13 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
}
|
||||
|
||||
const queryTablesTabs: QueryTablesTab[] = useTabs
|
||||
const queryTablesTabs: NewQueryTablesTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.QueryTables,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as QueryTablesTab[];
|
||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||
) as NewQueryTablesTab[];
|
||||
let queryTablesTab: NewQueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||
|
||||
if (queryTablesTab) {
|
||||
useTabs.getState().activateTab(queryTablesTab);
|
||||
@@ -415,14 +416,57 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
queryTablesTab = new QueryTablesTab({
|
||||
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
queryTablesTab = new NewQueryTablesTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
onLoadStartKey: startKey,
|
||||
},
|
||||
{
|
||||
container: this.container,
|
||||
}
|
||||
);
|
||||
|
||||
// const queryTablesTabs: QueryTablesTab[] = useTabs
|
||||
// .getState()
|
||||
// .getTabs(
|
||||
// ViewModels.CollectionTabKind.QueryTables,
|
||||
// (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
// ) as QueryTablesTab[];
|
||||
// let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||
|
||||
// if (queryTablesTab) {
|
||||
// useTabs.getState().activateTab(queryTablesTab);
|
||||
// } else {
|
||||
// this.documentIds([]);
|
||||
// let title = `Entities`;
|
||||
// if (userContext.apiType === "Cassandra") {
|
||||
// title = `Rows`;
|
||||
// }
|
||||
// const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
// databaseName: this.databaseId,
|
||||
// collectionName: this.id(),
|
||||
|
||||
// dataExplorerArea: Constants.Areas.Tab,
|
||||
// tabTitle: title,
|
||||
// });
|
||||
|
||||
// queryTablesTab = new QueryTablesTab(
|
||||
// {
|
||||
// tabKind: ViewModels.CollectionTabKind.QueryTables,
|
||||
// title: title,
|
||||
// tabPath: "",
|
||||
// collection: this,
|
||||
// node: this,
|
||||
// onLoadStartKey: startKey,
|
||||
// },
|
||||
// {
|
||||
// container: this.container,
|
||||
// }
|
||||
// );
|
||||
|
||||
useTabs.getState().activateNewTab(queryTablesTab);
|
||||
}
|
||||
|
||||
@@ -128,12 +128,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
notebooksTree.children.push(buildGalleryNotebooksTree());
|
||||
}
|
||||
|
||||
if (
|
||||
myNotebooksContentRoot &&
|
||||
((NotebookUtil.isPhoenixEnabled() &&
|
||||
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected) ||
|
||||
userContext.features.phoenix === false)
|
||||
) {
|
||||
if (myNotebooksContentRoot && useNotebook.getState().connectionInfo.status == ConnectionStatusType.Connected) {
|
||||
notebooksTree.children.push(buildMyNotebooksTree());
|
||||
}
|
||||
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
@@ -167,11 +162,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
myNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
container.openNotebook(item).then((hasOpened) => {
|
||||
if (
|
||||
hasOpened &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
userContext.features.phoenix === false
|
||||
) {
|
||||
if (hasOpened) {
|
||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||
}
|
||||
});
|
||||
@@ -190,11 +181,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
gitHubNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
container.openNotebook(item).then((hasOpened) => {
|
||||
if (
|
||||
hasOpened &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
userContext.features.phoenix === false
|
||||
) {
|
||||
if (hasOpened) {
|
||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||
}
|
||||
});
|
||||
@@ -226,7 +213,23 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
},
|
||||
},
|
||||
];
|
||||
gitHubNotebooksTree.contextMenu = manageGitContextMenu;
|
||||
const connectGitContextMenu: TreeNodeMenuItem[] = [
|
||||
{
|
||||
label: "Connect to GitHub",
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Connect to GitHub",
|
||||
<GitHubReposPanel
|
||||
explorer={container}
|
||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||
junoClientProp={container.notebookManager.junoClient}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
gitHubNotebooksTree.contextMenu = isConnected ? manageGitContextMenu : connectGitContextMenu;
|
||||
gitHubNotebooksTree.isExpanded = true;
|
||||
gitHubNotebooksTree.isAlphaSorted = true;
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import UserDefinedFunction from "./UserDefinedFunction";
|
||||
|
||||
export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public static readonly MyNotebooksTitle = "My Notebooks";
|
||||
public static readonly MyNotebooksScratchTitle = "My Notebooks Scratch";
|
||||
public static readonly GitHubReposTitle = "GitHub repos";
|
||||
|
||||
private static readonly DataTitle = "DATA";
|
||||
|
||||
@@ -22,7 +22,6 @@ export default class Trigger {
|
||||
public triggerType: ko.Observable<string>;
|
||||
public triggerOperation: ko.Observable<string>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: any) {
|
||||
this.nodeKind = "Trigger";
|
||||
this.container = container;
|
||||
@@ -35,7 +34,7 @@ export default class Trigger {
|
||||
this.triggerType = ko.observable(data.triggerType);
|
||||
}
|
||||
|
||||
public select(): void {
|
||||
public select() {
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Trigger node",
|
||||
@@ -44,8 +43,7 @@ export default class Trigger {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public static create(source: ViewModels.Collection, _event: MouseEvent): void {
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
||||
const trigger = <StoredProcedureDefinition>{
|
||||
id: "",
|
||||
@@ -101,7 +99,7 @@ export default class Trigger {
|
||||
}
|
||||
};
|
||||
|
||||
public delete(): void {
|
||||
public delete() {
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
"Confirm delete",
|
||||
"Are you sure you want to delete the trigger?",
|
||||
@@ -112,8 +110,7 @@ export default class Trigger {
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
() => {}
|
||||
(reason) => {}
|
||||
);
|
||||
},
|
||||
"Cancel",
|
||||
|
||||
@@ -64,7 +64,7 @@ export class GitHubOAuthService {
|
||||
return params.state;
|
||||
}
|
||||
|
||||
public async finishOAuth(params: IGitHubConnectorParams): Promise<void> {
|
||||
public async finishOAuth(params: IGitHubConnectorParams) {
|
||||
try {
|
||||
this.validateState(params.state);
|
||||
const response = await this.junoClient.getGitHubToken(params.code);
|
||||
@@ -113,7 +113,7 @@ export class GitHubOAuthService {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public resetToken(): void {
|
||||
public resetToken() {
|
||||
this.token(undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ describe("Pinned repos", () => {
|
||||
});
|
||||
|
||||
it("updatePinnedRepos invokes pinned repos subscribers", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const callback = jest.fn().mockImplementation(() => {});
|
||||
const callback = jest.fn().mockImplementation((pinnedRepos: IPinnedRepo[]) => {});
|
||||
|
||||
junoClient.subscribeToPinnedRepos(callback);
|
||||
const response = await junoClient.updatePinnedRepos(samplePinnedRepos);
|
||||
@@ -61,8 +60,7 @@ describe("Pinned repos", () => {
|
||||
});
|
||||
|
||||
it("getPinnedRepos invokes pinned repos subscribers", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const callback = jest.fn().mockImplementation(() => {});
|
||||
const callback = jest.fn().mockImplementation((pinnedRepos: IPinnedRepo[]) => {});
|
||||
|
||||
junoClient.subscribeToPinnedRepos(callback);
|
||||
const response = await junoClient.getPinnedRepos("scope");
|
||||
@@ -155,7 +153,7 @@ describe("Gallery", () => {
|
||||
it("getSampleNotebooks", async () => {
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.getSampleNotebooks();
|
||||
@@ -167,7 +165,7 @@ describe("Gallery", () => {
|
||||
it("getPublicNotebooks", async () => {
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.getPublicNotebooks();
|
||||
@@ -180,7 +178,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.getNotebookInfo(id);
|
||||
@@ -193,7 +191,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
text: () => undefined as undefined,
|
||||
text: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.getNotebookContent(id);
|
||||
@@ -206,7 +204,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
const response = await junoClient.increaseNotebookViews(id);
|
||||
|
||||
@@ -220,7 +218,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.increaseNotebookDownloadCount(id);
|
||||
@@ -245,7 +243,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.favoriteNotebook(id);
|
||||
@@ -270,7 +268,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.unfavoriteNotebook(id);
|
||||
@@ -294,7 +292,7 @@ describe("Gallery", () => {
|
||||
it("getFavoriteNotebooks", async () => {
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.getFavoriteNotebooks();
|
||||
@@ -317,7 +315,7 @@ describe("Gallery", () => {
|
||||
it("getPublishedNotebooks", async () => {
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.getPublishedNotebooks();
|
||||
@@ -341,7 +339,7 @@ describe("Gallery", () => {
|
||||
const id = "id";
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.deleteNotebook(id);
|
||||
@@ -371,7 +369,7 @@ describe("Gallery", () => {
|
||||
const addLinkToNotebookViewer = true;
|
||||
window.fetch = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCodes.OK,
|
||||
json: () => undefined as undefined,
|
||||
json: () => undefined as any,
|
||||
});
|
||||
|
||||
const response = await junoClient.publishNotebook(name, description, tags, thumbnailUrl, content);
|
||||
|
||||
@@ -62,7 +62,7 @@ export interface IPublishNotebookRequest {
|
||||
description: string;
|
||||
tags: string[];
|
||||
thumbnailUrl: string;
|
||||
content: unknown;
|
||||
content: any;
|
||||
addLinkToNotebookViewer: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
|
||||
@@ -13,6 +15,7 @@ export interface IPhoenixConnectionInfoResult {
|
||||
}
|
||||
export interface IProvosionData {
|
||||
cosmosEndpoint: string;
|
||||
resourceId: string;
|
||||
dbAccountName: string;
|
||||
aadToken: string;
|
||||
resourceGroup: string;
|
||||
@@ -23,7 +26,11 @@ export class PhoenixClient {
|
||||
provisionData: IProvosionData
|
||||
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
||||
try {
|
||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/allocate`, {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
|
||||
method: "POST",
|
||||
headers: PhoenixClient.getHeaders(),
|
||||
body: JSON.stringify(provisionData),
|
||||
@@ -31,20 +38,31 @@ export class PhoenixClient {
|
||||
let data: IPhoenixConnectionInfoResult;
|
||||
if (response.status === HttpStatusCodes.OK) {
|
||||
data = await response.json();
|
||||
if (data && data.notebookServerUrl) {
|
||||
connectionStatus.status = ConnectionStatusType.Connected;
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
}
|
||||
} else {
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static getPhoenixEndpoint(): string {
|
||||
const phoenixEndpoint =
|
||||
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||
const phoenixEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
|
||||
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
||||
console.error(error);
|
||||
@@ -55,7 +73,7 @@ export class PhoenixClient {
|
||||
}
|
||||
|
||||
public getPhoenixContainerPoolingEndPoint(): string {
|
||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer`;
|
||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/containerpooling`;
|
||||
}
|
||||
private static getHeaders(): HeadersInit {
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
|
||||
@@ -20,7 +20,6 @@ export type Features = {
|
||||
readonly enableKoResourceTree: boolean;
|
||||
readonly hostedDataExplorer: boolean;
|
||||
readonly junoEndpoint?: string;
|
||||
readonly phoenixEndpoint?: string;
|
||||
readonly livyEndpoint?: string;
|
||||
readonly notebookBasePath?: string;
|
||||
readonly notebookServerToken?: string;
|
||||
@@ -69,7 +68,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
||||
mongoProxyEndpoint: get("mongoproxyendpoint"),
|
||||
mongoProxyAPIs: get("mongoproxyapis"),
|
||||
junoEndpoint: get("junoendpoint"),
|
||||
phoenixEndpoint: get("phoenixendpoint"),
|
||||
livyEndpoint: get("livyendpoint"),
|
||||
notebookBasePath: get("notebookbasepath"),
|
||||
notebookServerToken: get("notebookservertoken"),
|
||||
|
||||
@@ -30,7 +30,7 @@ describe("GalleryUtils", () => {
|
||||
});
|
||||
|
||||
it("downloadItem shows dialog in data explorer", () => {
|
||||
const container = new Explorer();
|
||||
const container = {} as Explorer;
|
||||
GalleryUtils.downloadItem(container, undefined, galleryItem, undefined);
|
||||
|
||||
expect(useDialog.getState().visible).toBe(true);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
SortBy,
|
||||
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -226,89 +225,67 @@ export function downloadItem(
|
||||
const name = data.name;
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
`Download to ${useNotebook.getState().notebookFolderName}`,
|
||||
undefined,
|
||||
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
||||
"Download",
|
||||
async () => {
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
await container.allocateContainer();
|
||||
}
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
downloadNotebookItem(name, data, junoClient, container, onComplete);
|
||||
} else {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Failed to Connect",
|
||||
"Failed to connect to temporary workspace. Please refresh the page and try again."
|
||||
);
|
||||
}
|
||||
},
|
||||
"Cancel",
|
||||
undefined,
|
||||
container.getDownloadModalConent(name)
|
||||
);
|
||||
}
|
||||
export async function downloadNotebookItem(
|
||||
fileName: string,
|
||||
data: IGalleryItem,
|
||||
junoClient: JunoClient,
|
||||
container: Explorer,
|
||||
onComplete: (item: IGalleryItem) => void
|
||||
) {
|
||||
const clearInProgressMessage = logConsoleProgress(
|
||||
`Downloading ${fileName} to ${useNotebook.getState().notebookFolderName}`
|
||||
);
|
||||
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
||||
notebookId: data.id,
|
||||
downloadCount: data.downloads,
|
||||
isSample: data.isSample,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await junoClient.getNotebookContent(data.id);
|
||||
if (!response.data) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`);
|
||||
}
|
||||
|
||||
const notebook = JSON.parse(response.data) as Notebook;
|
||||
removeNotebookViewerLink(notebook, data.newCellId);
|
||||
|
||||
if (!data.isSample) {
|
||||
const metadata = notebook.metadata as { [name: string]: unknown };
|
||||
metadata.untrusted = true;
|
||||
}
|
||||
|
||||
await container.importAndOpenContent(data.name, JSON.stringify(notebook));
|
||||
logConsoleInfo(`Successfully downloaded ${data.name} to ${useNotebook.getState().notebookFolderName}`);
|
||||
|
||||
const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id);
|
||||
if (increaseDownloadResponse.data) {
|
||||
traceSuccess(
|
||||
Action.NotebooksGalleryDownload,
|
||||
{ notebookId: data.id, downloadCount: increaseDownloadResponse.data.downloads, isSample: data.isSample },
|
||||
startKey
|
||||
const clearInProgressMessage = logConsoleProgress(
|
||||
`Downloading ${name} to ${useNotebook.getState().notebookFolderName}`
|
||||
);
|
||||
onComplete(increaseDownloadResponse.data);
|
||||
}
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.NotebooksGalleryDownload,
|
||||
{
|
||||
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
||||
notebookId: data.id,
|
||||
downloadCount: data.downloads,
|
||||
isSample: data.isSample,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
});
|
||||
|
||||
handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`);
|
||||
}
|
||||
try {
|
||||
const response = await junoClient.getNotebookContent(data.id);
|
||||
if (!response.data) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`);
|
||||
}
|
||||
|
||||
clearInProgressMessage();
|
||||
const notebook = JSON.parse(response.data) as Notebook;
|
||||
removeNotebookViewerLink(notebook, data.newCellId);
|
||||
|
||||
if (!data.isSample) {
|
||||
const metadata = notebook.metadata as { [name: string]: unknown };
|
||||
metadata.untrusted = true;
|
||||
}
|
||||
|
||||
await container.importAndOpenContent(data.name, JSON.stringify(notebook));
|
||||
logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`);
|
||||
|
||||
const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id);
|
||||
if (increaseDownloadResponse.data) {
|
||||
traceSuccess(
|
||||
Action.NotebooksGalleryDownload,
|
||||
{ notebookId: data.id, downloadCount: increaseDownloadResponse.data.downloads, isSample: data.isSample },
|
||||
startKey
|
||||
);
|
||||
onComplete(increaseDownloadResponse.data);
|
||||
}
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.NotebooksGalleryDownload,
|
||||
{
|
||||
notebookId: data.id,
|
||||
downloadCount: data.downloads,
|
||||
isSample: data.isSample,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`);
|
||||
}
|
||||
|
||||
clearInProgressMessage();
|
||||
},
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export const removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
|
||||
if (!newCellId) {
|
||||
return;
|
||||
|
||||
@@ -69,11 +69,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
|
||||
const tabToTheRight = updatedTabs[tabIndex];
|
||||
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
|
||||
const newActiveTab = tabToTheRight ?? lastOpenTab;
|
||||
set({ activeTab: newActiveTab });
|
||||
if (newActiveTab) {
|
||||
newActiveTab.onActivate();
|
||||
}
|
||||
set({ activeTab: tabToTheRight || lastOpenTab });
|
||||
}
|
||||
|
||||
set({ openedTabs: updatedTabs });
|
||||
|
||||
61
src/indexO.html
Normal file
61
src/indexO.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html public "-//W3C//DTD HTML 4.0//en">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Azure Cosmos DB Emulator</title>
|
||||
|
||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="header HeaderBg">
|
||||
<div class="items">
|
||||
<img class="DocDBicon" src="/CosmosDB_20170829.svg" alt="Azure Cosmos DB" />
|
||||
<a class="createdocdbacnt" href="https://aka.ms/documentdbcreate" target="_blank">
|
||||
Create an Azure Cosmos DB account Old<img class="rightarrowimg" src="/Arrow.svg" alt="" />
|
||||
</a>
|
||||
<span class="title">Azure Cosmos DB Emulator</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="fixedleftpane">
|
||||
<div
|
||||
id="Quickstart"
|
||||
data-bind="click: quickstart_click, css:{ topSelected: navigationSelection() === 'quickstart' }"
|
||||
>
|
||||
<img id="imgiconwidth1" src="/Quickstart.svg" alt="Open Quick Start" />
|
||||
<span class="menuQuickStart">Quickstart</span>
|
||||
</div>
|
||||
|
||||
<div id="Explorer" data-bind="click: explorer_click, css:{ topSelected: navigationSelection() === 'explorer' }">
|
||||
<img id="imgiconwidth1" src="/Explorer.svg" alt="Open Data Explorer" />
|
||||
<span class="menuExplorer">Explorer</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a class="feedbackstyle" href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Emulator%20Feedback">
|
||||
<img id="imgiconwidth1" src="/Feedback.svg" alt="Report Issue" />
|
||||
<span class="menuExplorer">Report Issue</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<iframe
|
||||
name="quickstart"
|
||||
class="iframe"
|
||||
src="quickstart.html"
|
||||
data-bind="visible: navigationSelection() === 'quickstart'"
|
||||
>
|
||||
</iframe>
|
||||
|
||||
<iframe
|
||||
name="explorer"
|
||||
class="iframe"
|
||||
src="explorer.html?platform=Emulator"
|
||||
data-bind="visible: navigationSelection() === 'explorer'"
|
||||
>
|
||||
</iframe>
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,13 +15,18 @@
|
||||
"target": "es2017",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"lib": ["es5", "es6", "dom"],
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6",
|
||||
"dom"
|
||||
],
|
||||
"jsx": "react",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true,
|
||||
"types": ["jest"],
|
||||
"baseUrl": "src"
|
||||
"types": [
|
||||
"jest"
|
||||
]
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": [
|
||||
@@ -37,6 +42,11 @@
|
||||
"includes": "./src/SelfServe/Documentation",
|
||||
"disableSources": true
|
||||
},
|
||||
"include": ["src", "./src/**/*", "./utils/**/*"],
|
||||
"exclude": ["./src/**/__mocks__/**/*"]
|
||||
}
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./utils/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/__mocks__/**/*"
|
||||
]
|
||||
}
|
||||
@@ -227,7 +227,6 @@ module.exports = function (_env = {}, argv = {}) {
|
||||
rules,
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve(__dirname, "src"), "node_modules"],
|
||||
alias: {
|
||||
process: "process/browser",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user