mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 19:01:28 +00:00
Compare commits
96 Commits
fixed-pric
...
migrate_qu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cec6b27d2e | ||
|
|
7d905159c6 | ||
|
|
6ca8e3c6f4 | ||
|
|
f7e7240010 | ||
|
|
acc095a482 | ||
|
|
288e6410f3 | ||
|
|
9ac3392271 | ||
|
|
9a908dde9a | ||
|
|
ddd2e63fe7 | ||
|
|
34c59b4872 | ||
|
|
7d28af4fc7 | ||
|
|
50b99ceb42 | ||
|
|
15a26d6500 | ||
|
|
a8150af269 | ||
|
|
6a9a0156a3 | ||
|
|
ead28f043f | ||
|
|
b05e5a2145 | ||
|
|
5e8aa491ba | ||
|
|
a730c08292 | ||
|
|
3dce5cd129 | ||
|
|
7c186c3ef2 | ||
|
|
d8840a0dfd | ||
|
|
f853c4ec2f | ||
|
|
9bf5f48165 | ||
|
|
0b2a204b70 | ||
|
|
c28593b752 | ||
|
|
4cbbef9574 | ||
|
|
300c952a9b | ||
|
|
38c3761260 | ||
|
|
3032f689b6 | ||
|
|
8b30af3d9e | ||
|
|
e10240bd7a | ||
|
|
ae9c27795e | ||
|
|
d7997d716e | ||
|
|
af0dc3094b | ||
|
|
665270296f | ||
|
|
2d945c8231 | ||
|
|
8866976bb4 | ||
|
|
d10f3c69f1 | ||
|
|
7e4f030547 | ||
|
|
05f46dd635 | ||
|
|
65882ea831 | ||
|
|
95c9b7ee31 | ||
|
|
39dd293fc1 | ||
|
|
126a572078 | ||
|
|
597357e62d | ||
|
|
6c545c454d | ||
|
|
d4817de14e | ||
|
|
8eeda41021 | ||
|
|
960cd9fc55 | ||
|
|
9ec0ac9f54 | ||
|
|
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 |
@@ -21,16 +21,8 @@ src/Common/MongoUtility.ts
|
|||||||
src/Common/NotificationsClientBase.ts
|
src/Common/NotificationsClientBase.ts
|
||||||
src/Common/QueriesClient.ts
|
src/Common/QueriesClient.ts
|
||||||
src/Common/Splitter.ts
|
src/Common/Splitter.ts
|
||||||
src/Config.ts
|
|
||||||
src/Contracts/ActionContracts.ts
|
|
||||||
src/Contracts/DataModels.ts
|
|
||||||
src/Contracts/Diagnostics.ts
|
|
||||||
src/Contracts/ExplorerContracts.ts
|
|
||||||
src/Contracts/Versions.ts
|
|
||||||
src/Contracts/ViewModels.ts
|
|
||||||
src/Controls/Heatmap/Heatmap.test.ts
|
src/Controls/Heatmap/Heatmap.test.ts
|
||||||
src/Controls/Heatmap/Heatmap.ts
|
src/Controls/Heatmap/Heatmap.ts
|
||||||
src/Controls/Heatmap/HeatmapDatatypes.ts
|
|
||||||
src/Definitions/datatables.d.ts
|
src/Definitions/datatables.d.ts
|
||||||
src/Definitions/gif.d.ts
|
src/Definitions/gif.d.ts
|
||||||
src/Definitions/globals.d.ts
|
src/Definitions/globals.d.ts
|
||||||
@@ -44,29 +36,10 @@ src/Definitions/png.d.ts
|
|||||||
src/Definitions/svg.d.ts
|
src/Definitions/svg.d.ts
|
||||||
src/Explorer/ComponentRegisterer.test.ts
|
src/Explorer/ComponentRegisterer.test.ts
|
||||||
src/Explorer/ComponentRegisterer.ts
|
src/Explorer/ComponentRegisterer.ts
|
||||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
|
|
||||||
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
||||||
src/Explorer/Controls/DynamicList/DynamicList.test.ts
|
|
||||||
src/Explorer/Controls/DynamicList/DynamicListComponent.ts
|
|
||||||
src/Explorer/Controls/Editor/EditorComponent.ts
|
src/Explorer/Controls/Editor/EditorComponent.ts
|
||||||
src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
|
|
||||||
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
|
||||||
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
||||||
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarDropDown.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarItem.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarSeperator.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarToggle.ts
|
|
||||||
src/Explorer/Controls/Toolbar/KeyCodes.ts
|
|
||||||
src/Explorer/Controls/Toolbar/Toolbar.ts
|
|
||||||
src/Explorer/Controls/Toolbar/ToolbarAction.ts
|
|
||||||
src/Explorer/Controls/Toolbar/ToolbarDropDown.ts
|
|
||||||
src/Explorer/Controls/Toolbar/ToolbarToggle.ts
|
|
||||||
src/Explorer/Controls/Toolbar/Utilities.ts
|
|
||||||
src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
|
src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
|
||||||
src/Explorer/DataSamples/ContainerSampleGenerator.ts
|
src/Explorer/DataSamples/ContainerSampleGenerator.ts
|
||||||
src/Explorer/DataSamples/DataSamplesUtil.test.ts
|
src/Explorer/DataSamples/DataSamplesUtil.test.ts
|
||||||
@@ -107,8 +80,8 @@ src/Explorer/Tables/DataTable/CacheBase.ts
|
|||||||
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
|
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
|
# src/Explorer/Tables/DataTable/DataTableOperationManager.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableOperations.ts
|
# src/Explorer/Tables/DataTable/DataTableOperations.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
||||||
src/Explorer/Tables/DataTable/TableCommands.ts
|
src/Explorer/Tables/DataTable/TableCommands.ts
|
||||||
src/Explorer/Tables/DataTable/TableEntityCache.ts
|
src/Explorer/Tables/DataTable/TableEntityCache.ts
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg
|
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
version="1.1"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<path
|
<path
|
||||||
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
|
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
|
||||||
transform="scale(0.5)"
|
transform="scale(0.5)" fill="#000" stroke="#000">
|
||||||
fill="#000"
|
|
||||||
stroke="#CCC"
|
|
||||||
>
|
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 449 B |
@@ -37,8 +37,8 @@ module.exports = {
|
|||||||
global: {
|
global: {
|
||||||
branches: 25,
|
branches: 25,
|
||||||
functions: 25,
|
functions: 25,
|
||||||
lines: 30,
|
lines: 29.5,
|
||||||
statements: 30,
|
statements: 29.5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,337 +3,358 @@
|
|||||||
@import "../Common/Constants";
|
@import "../Common/Constants";
|
||||||
|
|
||||||
.query-panel {
|
.query-panel {
|
||||||
display: table;
|
display: table;
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid #DDDDDD;
|
border-top: 1px solid #dddddd;
|
||||||
/*[{environment-commandbar-toolbar-separator}]*/
|
/*[{environment-commandbar-toolbar-separator}]*/
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{plugin-background-color}]*/
|
/*[{plugin-background-color}]*/
|
||||||
padding: 2px 0px 0px 2px;
|
padding: 2px 0px 0px 2px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row {
|
.query-panel .row {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row .cell {
|
.query-panel .row .cell {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel.transition-in {
|
.query-panel.transition-in {
|
||||||
display: table;
|
display: table;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
-webkit-transition: top 2s linear;
|
-webkit-transition: top 2s linear;
|
||||||
-ms-transition: top 2s linear;
|
-ms-transition: top 2s linear;
|
||||||
-moz-transition: top 2s linear;
|
-moz-transition: top 2s linear;
|
||||||
-khtml-transition: top 2s linear;
|
-khtml-transition: top 2s linear;
|
||||||
-o-transition: top 2s linear;
|
-o-transition: top 2s linear;
|
||||||
transition: top 2s linear;
|
transition: top 2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder {
|
.query-builder {
|
||||||
width:100%;
|
width: 100%;
|
||||||
padding-right: @DefaultSpace;
|
padding-right: @DefaultSpace;
|
||||||
border-bottom: 1px solid @BaseMedium;
|
border-bottom: 1px solid @BaseMedium;
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar {
|
.query-builder-toolbar {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{plugin-background-color}]*/
|
/*[{plugin-background-color}]*/
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-bottom: 1px solid #DDDDDD;
|
border-bottom: 1px solid #dddddd;
|
||||||
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group {
|
.query-builder-toolbar .query-toolbar-group {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin: 2px 0px;
|
margin: 2px 0px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: solid transparent;
|
border: solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
|
||||||
outline: 2px solid dodgerblue;
|
outline: 2px solid dodgerblue;
|
||||||
/*[2px solid {common-common-controls-button-border-hover}]*/
|
/*[2px solid {common-common-controls-button-border-hover}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
|
||||||
background-color: #CCCEDB;
|
background-color: #cccedb;
|
||||||
/*[{common-controls-button-hover-background}]*/
|
/*[{common-controls-button-hover-background}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
|
||||||
background-color: #E6E7ED;
|
background-color: #e6e7ed;
|
||||||
/*[{common-controls-inner-tab-active-background}]*/
|
/*[{common-controls-inner-tab-active-background}]*/
|
||||||
outline: none
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{common-controls-button-disabled-background}]*/
|
/*[{common-controls-button-disabled-background}]*/
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tableContainer {
|
.tableContainer {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
.flex-direction();
|
.flex-direction();
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tablesQueryTab{
|
.tablesQueryTab {
|
||||||
padding-left: @MediumSpace;
|
padding-left: @MediumSpace;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom:@LargeSpace;
|
margin-bottom: 60px;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-error-Img {
|
.entity-error-Img {
|
||||||
width: @WarningErrorIconSize;
|
width: @WarningErrorIconSize;
|
||||||
height: @WarningErrorIconSize;
|
height: @WarningErrorIconSize;
|
||||||
margin: @DefaultSpace 0px 0px @SmallSpace;
|
margin: @DefaultSpace 0px 0px @SmallSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel {
|
.query-editor-panel {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-text {
|
.query-editor-text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
border: solid 1px #A9ACB3;
|
border: solid 1px #a9acb3;
|
||||||
/*[{plugin-textbox-disabled-color}]*/
|
/*[{plugin-textbox-disabled-color}]*/
|
||||||
resize: none;
|
resize: none;
|
||||||
margin-top: -39px;
|
margin-top: -39px;
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-bar {
|
.error-bar {
|
||||||
padding: @LargeSpace 34px @MediumSpace 24px;
|
padding: @LargeSpace 34px @MediumSpace 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
background-color: @BaseLow;
|
background-color: @BaseLow;
|
||||||
padding: @DefaultSpace;
|
padding: @DefaultSpace;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-text {
|
.error-text {
|
||||||
padding-left: @MediumSpace;
|
padding-left: @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-text-invalid {
|
.query-editor-text-invalid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
border: 1px solid #e51400;
|
border: 1px solid #e51400;
|
||||||
resize: none;
|
resize: none;
|
||||||
margin-top: -30px;
|
margin-top: -30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel .warning-bar {
|
.query-editor-panel .warning-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{plugin-background-color}]*/
|
/*[{plugin-background-color}]*/
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -24px;
|
top: -24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel .warning-bar .warning-message {
|
.query-editor-panel .warning-bar .warning-message {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel .warning-bar .warning-message .warning-text {
|
.query-editor-panel .warning-bar .warning-message .warning-text {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel{
|
.advanced-options-panel {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-heading .advanced-title {
|
.advanced-options-panel .advanced-heading .advanced-title {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-left: 27px;
|
margin-left: 27px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options {
|
.advanced-options-panel .advanced-options {
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
input::-webkit-outer-spin-button,
|
input::-webkit-outer-spin-button,
|
||||||
input::-webkit-inner-spin-button {
|
input::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .top .top-input {
|
.advanced-options-panel .advanced-options .top .top-input {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
word-spacing: normal;
|
word-spacing: normal;
|
||||||
color: #1E1E1E;
|
color: #1e1e1e;
|
||||||
/*[{common-controls-button-foreground}]*/
|
/*[{common-controls-button-foreground}]*/
|
||||||
border: 1px solid #CCCEDB;
|
border: 1px solid #cccedb;
|
||||||
/*[1px solid {plugin-textbox-border-color}]*/
|
/*[1px solid {plugin-textbox-border-color}]*/
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .top .invalid-top {
|
.advanced-options-panel .advanced-options .top .invalid-top {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .select {
|
.advanced-options-panel .advanced-options .select {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-icon {
|
.advanced-options-icon {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .select .select-options-text {
|
.advanced-options-panel .advanced-options .select .select-options-text {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .select .select-options-link {
|
.advanced-options-panel .advanced-options .select .select-options-link {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row .column-headers .Field {
|
.query-panel .row .column-headers .Field {
|
||||||
padding-left: 95px;
|
padding-left: 95px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table {
|
.clause-table {
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-row {
|
.clause-table-row {
|
||||||
display: row;
|
display: row;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell {
|
.clause-table-cell {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-column>button,
|
.action-column > button,
|
||||||
.group-control-header>button,
|
.group-control-header > button,
|
||||||
.group-indicator-column>button {
|
.group-indicator-column > button {
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-control-header>button:disabled {
|
.group-control-header > button:disabled {
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-field {
|
.clause-table-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid #bbbbbb;
|
border: 1px solid #bbbbbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell button {
|
.clause-table-cell button {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell input[type="checkbox"] {
|
.clause-table-cell input[type="checkbox"] {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.and-or-svg {
|
.and-or-svg {
|
||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
margin-right: -5px;
|
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 {
|
.scroll-box {
|
||||||
border-bottom: 1px transparent #DDD;
|
border-bottom: 1px transparent #ddd;
|
||||||
/*[1px solid {plugin-table-border-color}]*/
|
/*[1px solid {plugin-table-border-color}]*/
|
||||||
border-top: 1px transparent #DDD;
|
border-top: 1px transparent #ddd;
|
||||||
/*[1px solid {plugin-table-border-color}]*/
|
/*[1px solid {plugin-table-border-color}]*/
|
||||||
max-height: 20vh;
|
max-height: 20vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.and-or-column,
|
.and-or-column,
|
||||||
.and-or-header {
|
.and-or-header {
|
||||||
min-width: 65px;
|
min-width: 65px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator-column,
|
.operator-column,
|
||||||
.operator-header {
|
.operator-header {
|
||||||
min-width: 65px;
|
min-width: 65px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-header,
|
.field-header,
|
||||||
.field-column {
|
.field-column {
|
||||||
min-width: 125px;
|
min-width: 125px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-header,
|
.type-header,
|
||||||
.type-column {
|
.type-column {
|
||||||
min-width: 85px;
|
min-width: 85px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.and-or-column,
|
.and-or-column,
|
||||||
@@ -345,41 +366,41 @@ input::-webkit-inner-spin-button {
|
|||||||
.type-header,
|
.type-header,
|
||||||
.type-column,
|
.type-column,
|
||||||
.action-header {
|
.action-header {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value-header,
|
.value-header,
|
||||||
.value-column,
|
.value-column,
|
||||||
.time-column {
|
.time-column {
|
||||||
min-width: 230px;
|
min-width: 230px;
|
||||||
padding: 0px 4px 0px 0px;
|
padding: 0px 4px 0px 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-control-header,
|
.group-control-header,
|
||||||
.group-control-column {
|
.group-control-column {
|
||||||
min-width: 25px;
|
min-width: 25px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-indicator-table {
|
.group-indicator-table {
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
min-height: 24px
|
min-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-indicator-column {
|
.group-indicator-column {
|
||||||
min-width: 21px;
|
min-width: 21px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
border-style: none;
|
border-style: none;
|
||||||
height: 29px;
|
height: 29px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell.action-column,
|
.clause-table-cell.action-column,
|
||||||
.clause-table-cell.action-header {
|
.clause-table-cell.action-header {
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
padding-left: @SmallSpace;
|
padding-left: @SmallSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-header,
|
.action-header,
|
||||||
@@ -388,15 +409,14 @@ input::-webkit-inner-spin-button {
|
|||||||
.operator-header,
|
.operator-header,
|
||||||
.value-header,
|
.value-header,
|
||||||
.and-or-header {
|
.and-or-header {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-background {
|
.header-background {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*.type-header {
|
/*.type-header {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
@@ -410,111 +430,165 @@ input::-webkit-inner-spin-button {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
.clause-table-field[readonly] {
|
.clause-table-field[readonly] {
|
||||||
background-color: #EEEEF2;
|
background-color: #eeeef2;
|
||||||
/*[{plugin-table-header-background-color}]*/
|
/*[{plugin-table-header-background-color}]*/
|
||||||
border: 1px solid #CCCEDB;
|
border: 1px solid #cccedb;
|
||||||
/*[{plugin-table-border-color}]*/
|
/*[{plugin-table-border-color}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause-title {
|
.addClause-title {
|
||||||
/*[{common-common-controls-button-border-hover}]*/
|
/*[{common-common-controls-button-border-hover}]*/
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: -5px;
|
margin-left: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause {
|
.addClause {
|
||||||
width: 125px;
|
width: 125px;
|
||||||
padding: 8px 0px 5px 5px;
|
padding: 8px 0px 5px 5px;
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause:hover {
|
.addClause:hover {
|
||||||
.hover();
|
.hover();
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause:active {
|
.addClause:active {
|
||||||
.active();
|
.active();
|
||||||
border: 1px dashed @AccentMedium;
|
border: 1px dashed @AccentMedium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-row {
|
.clause-table-row {
|
||||||
min-width: 550px;
|
min-width: 550px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-field field-column {
|
.clause-table-field field-column {
|
||||||
min-width: 75px;
|
min-width: 75px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-field field-input {
|
.clause-table-field field-input {
|
||||||
min-width: 54px;
|
min-width: 54px;
|
||||||
margin-left: -78px;
|
margin-left: -78px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row .spacing {
|
.query-panel .row .spacing {
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .divider.horizontal {
|
.query-panel .divider.horizontal {
|
||||||
height: 10px;
|
height: 10px;
|
||||||
width: 100%
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-div {
|
.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 {
|
.querybuilder-cancelImg {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addclauseProperty-Img {
|
.addclauseProperty-Img {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-Add-Cancel {
|
.entity-Add-Cancel {
|
||||||
padding: @DefaultSpace @SmallSpace @SmallSpace;
|
padding: @DefaultSpace @SmallSpace @SmallSpace;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-Add-Cancel:hover {
|
.entity-Add-Cancel:hover {
|
||||||
.hover();
|
.hover();
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-Add-Cancel:active {
|
.entity-Add-Cancel:active {
|
||||||
.active();
|
.active();
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-isDisabled {
|
.query-builder-isDisabled {
|
||||||
border: 1px solid #CCCEDB;
|
border: 1px solid #cccedb;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-value-text {
|
.edit-value-text {
|
||||||
padding-left: @DefaultSpace;
|
padding-left: @DefaultSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-triangle {
|
.expand-triangle {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-triangle-right {
|
.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) {
|
@media only screen and (max-width: 1200px) {
|
||||||
@@ -524,4 +598,4 @@ input::-webkit-inner-spin-button {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|||||||
@@ -2357,6 +2357,8 @@ a:link {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@@ -2832,6 +2834,8 @@ a:link {
|
|||||||
|
|
||||||
#explorerNotificationConsole {
|
#explorerNotificationConsole {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uniqueIndexesContainer {
|
.uniqueIndexesContainer {
|
||||||
|
|||||||
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 ko from "knockout";
|
||||||
import * as ReactBindingHandler from "./ReactBindingHandler";
|
import * as ReactBindingHandler from "./ReactBindingHandler";
|
||||||
import "../Explorer/Tables/DataTable/DataTableBindingManager";
|
|
||||||
|
|
||||||
export class BindingHandlersRegisterer {
|
export class BindingHandlersRegisterer {
|
||||||
public static registerBindingHandlers() {
|
public static registerBindingHandlers() {
|
||||||
ko.bindingHandlers.setTemplateReady = {
|
ko.bindingHandlers.setTemplateReady = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { FunctionComponent } from "react";
|
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
import { NormalizedEventKey } from "./Constants";
|
||||||
|
|
||||||
export interface CollapsedResourceTreeProps {
|
export interface CollapsedResourceTreeProps {
|
||||||
toggleLeftPaneExpanded: () => void;
|
toggleLeftPaneExpanded: () => void;
|
||||||
@@ -11,6 +12,21 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
|||||||
toggleLeftPaneExpanded,
|
toggleLeftPaneExpanded,
|
||||||
isLeftPaneExpanded,
|
isLeftPaneExpanded,
|
||||||
}: CollapsedResourceTreeProps): JSX.Element => {
|
}: CollapsedResourceTreeProps): JSX.Element => {
|
||||||
|
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusButton.current) {
|
||||||
|
focusButton.current.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
toggleLeftPaneExpanded();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||||
<div className="main-nav nav">
|
<div className="main-nav nav">
|
||||||
@@ -21,11 +37,14 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Expand Tree"
|
aria-label="Expand Tree"
|
||||||
|
onClick={toggleLeftPaneExpanded}
|
||||||
|
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||||
|
ref={focusButton}
|
||||||
>
|
>
|
||||||
<span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
|
<span className="leftarrowCollapsed">
|
||||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||||
</span>
|
</span>
|
||||||
<span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
|
<span className="collectionCollapsed">
|
||||||
<span>{userContext.apiType} API</span>
|
<span>{userContext.apiType} API</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export class Flights {
|
|||||||
public static readonly AutoscaleTest = "autoscaletest";
|
public static readonly AutoscaleTest = "autoscaletest";
|
||||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||||
|
public static readonly Phoenix = "phoenix";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -337,6 +338,12 @@ export enum ConflictOperationType {
|
|||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ConnectionStatusType {
|
||||||
|
Connecting = "Connecting",
|
||||||
|
Connected = "Connected",
|
||||||
|
Failed = "Connection Failed",
|
||||||
|
}
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
label={entityValueLabel && entityValueLabel}
|
label={entityValueLabel && entityValueLabel}
|
||||||
className="addEntityTextField"
|
className="addEntityTextField"
|
||||||
id="entityValueId"
|
// id="entityValueId"
|
||||||
autoFocus
|
autoFocus
|
||||||
disabled={isEntityValueDisable}
|
disabled={isEntityValueDisable}
|
||||||
type={entityValueType}
|
type={entityValueType}
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
|||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
import {
|
||||||
|
deleteDocument,
|
||||||
|
getEndpoint,
|
||||||
|
getFeatureEndpointOrDefault,
|
||||||
|
queryDocuments,
|
||||||
|
readDocument,
|
||||||
|
updateDocument,
|
||||||
|
} from "./MongoProxyClient";
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
|
||||||
@@ -246,4 +254,31 @@ describe("MongoProxyClient", () => {
|
|||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe("getFeatureEndpointOrDefault", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetConfigContext();
|
||||||
|
updateConfigContext({
|
||||||
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
|
});
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
"feature.mongoProxyEndpoint": "https://localhost:12901",
|
||||||
|
"feature.mongoProxyAPIs": "readDocument|createDocument",
|
||||||
|
});
|
||||||
|
const features = extractFeatures(params);
|
||||||
|
updateUserContext({
|
||||||
|
authType: AuthType.AAD,
|
||||||
|
features: features,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a local endpoint", () => {
|
||||||
|
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||||
|
expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a production endpoint", () => {
|
||||||
|
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||||
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as DataModels from "../Contracts/DataModels";
|
|||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { hasFlag } from "../Platform/Hosted/extractFeatures";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
@@ -78,7 +79,7 @@ export function queryDocuments(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint() || "";
|
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
@@ -141,7 +142,8 @@ export function readDocument(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -181,7 +183,7 @@ export function createDocument(
|
|||||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("createDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||||
@@ -225,7 +227,7 @@ export function updateDocument(
|
|||||||
? documentId.partitionKeyProperty
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -266,7 +268,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
? documentId.partitionKeyProperty
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -309,7 +311,7 @@ export function createMongoCollectionWithProxy(
|
|||||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(
|
.fetch(
|
||||||
@@ -333,8 +335,15 @@ export function createMongoCollectionWithProxy(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEndpoint(): string {
|
export function getFeatureEndpointOrDefault(feature: string): string {
|
||||||
let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
|
return hasFlag(userContext.features.mongoProxyAPIs, feature)
|
||||||
|
? getEndpoint(userContext.features.mongoProxyEndpoint)
|
||||||
|
: getEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEndpoint(customEndpoint?: string): string {
|
||||||
|
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
||||||
|
url += "/api/mongo/explorer";
|
||||||
|
|
||||||
if (userContext.authType === AuthType.EncryptedToken) {
|
if (userContext.authType === AuthType.EncryptedToken) {
|
||||||
url = url.replace("api/mongo", "api/guest/mongo");
|
url = url.replace("api/mongo", "api/guest/mongo");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { FunctionComponent } from "react";
|
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
import refreshImg from "../../images/refresh-cosmos.svg";
|
import refreshImg from "../../images/refresh-cosmos.svg";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
@@ -6,6 +6,7 @@ import Explorer from "../Explorer/Explorer";
|
|||||||
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
||||||
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
import { NormalizedEventKey } from "./Constants";
|
||||||
|
|
||||||
export interface ResourceTreeContainerProps {
|
export interface ResourceTreeContainerProps {
|
||||||
toggleLeftPaneExpanded: () => void;
|
toggleLeftPaneExpanded: () => void;
|
||||||
@@ -18,6 +19,22 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
|||||||
isLeftPaneExpanded,
|
isLeftPaneExpanded,
|
||||||
container,
|
container,
|
||||||
}: ResourceTreeContainerProps): JSX.Element => {
|
}: ResourceTreeContainerProps): JSX.Element => {
|
||||||
|
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLeftPaneExpanded) {
|
||||||
|
if (focusButton.current) {
|
||||||
|
focusButton.current.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
toggleLeftPaneExpanded();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
||||||
{/* Collections Window - - Start */}
|
{/* Collections Window - - Start */}
|
||||||
@@ -43,9 +60,11 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
|||||||
id="expandToggleLeftPaneButton"
|
id="expandToggleLeftPaneButton"
|
||||||
role="button"
|
role="button"
|
||||||
onClick={toggleLeftPaneExpanded}
|
onClick={toggleLeftPaneExpanded}
|
||||||
|
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Collapse Tree"
|
aria-label="Collapse Tree"
|
||||||
title="Collapse Tree"
|
title="Collapse Tree"
|
||||||
|
ref={focusButton}
|
||||||
>
|
>
|
||||||
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
<Stack horizontal tokens={sectionStackTokens}>
|
<Stack horizontal tokens={sectionStackTokens}>
|
||||||
<TextField
|
<TextField
|
||||||
label={entityPropertyLabel && entityPropertyLabel}
|
label={entityPropertyLabel && entityPropertyLabel}
|
||||||
id="entityPropertyId"
|
|
||||||
autoFocus
|
autoFocus
|
||||||
disabled={isPropertyTypeDisable}
|
disabled={isPropertyTypeDisable}
|
||||||
placeholder={entityPropertyPlaceHolder}
|
placeholder={entityPropertyPlaceHolder}
|
||||||
@@ -109,7 +108,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
onChange={onEntityTypeChange}
|
onChange={onEntityTypeChange}
|
||||||
options={options}
|
options={options}
|
||||||
disabled={isPropertyTypeDisable}
|
disabled={isPropertyTypeDisable}
|
||||||
id="entityTypeId"
|
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
/>
|
/>
|
||||||
<EntityValue
|
<EntityValue
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<TooltipHost content={children}>
|
<TooltipHost content={children}>
|
||||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ConnectionStatusType } from "../Common/Constants";
|
||||||
|
|
||||||
export interface DatabaseAccount {
|
export interface DatabaseAccount {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -496,3 +498,8 @@ export interface MemoryUsageInfo {
|
|||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContainerConnectionInfo {
|
||||||
|
status: ConnectionStatusType;
|
||||||
|
//need to add ram and rom info
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ describe("The Heatmap Control", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let heatmap: Heatmap;
|
let heatmap: Heatmap;
|
||||||
let theme: PortalTheme = 1;
|
const theme: PortalTheme = 1;
|
||||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||||
|
|
||||||
describe("drawHeatmap rendering", () => {
|
describe("drawHeatmap rendering", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -100,7 +100,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should show a no data message with a dark theme", () => {
|
it("should show a no data message with a dark theme", () => {
|
||||||
let data = {
|
const data = {
|
||||||
data: {
|
data: {
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
data: {
|
data: {
|
||||||
@@ -111,7 +111,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||||
document.body.innerHTML = divElement;
|
document.body.innerHTML = divElement;
|
||||||
|
|
||||||
handleMessage(data as MessageEvent);
|
handleMessage(data as MessageEvent);
|
||||||
@@ -120,7 +120,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should show a no data message with a white theme", () => {
|
it("should show a no data message with a white theme", () => {
|
||||||
let data = {
|
const data = {
|
||||||
data: {
|
data: {
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
data: {
|
data: {
|
||||||
@@ -131,7 +131,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||||
document.body.innerHTML = divElement;
|
document.body.innerHTML = divElement;
|
||||||
|
|
||||||
handleMessage(data as MessageEvent);
|
handleMessage(data as MessageEvent);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class Heatmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color: string = "#838383"): FontSettings {
|
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color = "#838383"): FontSettings {
|
||||||
return {
|
return {
|
||||||
family: StyleConstants.DataExplorerFont,
|
family: StyleConstants.DataExplorerFont,
|
||||||
size,
|
size,
|
||||||
@@ -78,9 +78,9 @@ export class Heatmap {
|
|||||||
// go thru all rows and create 2d matrix for heatmap...
|
// go thru all rows and create 2d matrix for heatmap...
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
output.yAxisPoints.push(rows[i]);
|
output.yAxisPoints.push(rows[i]);
|
||||||
let dataPoints: number[] = [];
|
const dataPoints: number[] = [];
|
||||||
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
||||||
let row: PartitionTimeStampToData = data[rows[i]];
|
const row: PartitionTimeStampToData = data[rows[i]];
|
||||||
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
|
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
|
||||||
}
|
}
|
||||||
output.dataPoints.push(dataPoints);
|
output.dataPoints.push(dataPoints);
|
||||||
@@ -193,7 +193,7 @@ export class Heatmap {
|
|||||||
this._getLayoutSettings(),
|
this._getLayoutSettings(),
|
||||||
this._getChartDisplaySettings()
|
this._getChartDisplaySettings()
|
||||||
);
|
);
|
||||||
let plotDiv: any = document.getElementById(Heatmap.elementId);
|
const plotDiv: any = document.getElementById(Heatmap.elementId);
|
||||||
plotDiv.on("plotly_click", (data: any) => {
|
plotDiv.on("plotly_click", (data: any) => {
|
||||||
let timeSelected: string = data.points[0].x;
|
let timeSelected: string = data.points[0].x;
|
||||||
timeSelected = timeSelected.replace(" ", "T");
|
timeSelected = timeSelected.replace(" ", "T");
|
||||||
@@ -205,7 +205,7 @@ export class Heatmap {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let output = [];
|
const output = [];
|
||||||
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
|
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
|
||||||
output.push(this._chartData.dataPoints[i][xAxisIndex]);
|
output.push(this._chartData.dataPoints[i][xAxisIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Icon, Label, Stack } from "@fluentui/react";
|
import { Icon, Label, Stack } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
export interface CollapsibleSectionProps {
|
export interface CollapsibleSectionProps {
|
||||||
@@ -30,6 +31,13 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onKeyPress = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
this.toggleCollapsed();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -39,6 +47,11 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
tokens={accordionStackTokens}
|
tokens={accordionStackTokens}
|
||||||
onClick={this.toggleCollapsed}
|
onClick={this.toggleCollapsed}
|
||||||
|
onKeyPress={this.onKeyPress}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-name="Advanced"
|
||||||
|
role="button"
|
||||||
|
aria-expanded={this.state.isExpanded}
|
||||||
>
|
>
|
||||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||||
<Label>{this.props.title}</Label>
|
<Label>{this.props.title}</Label>
|
||||||
|
|||||||
@@ -3,9 +3,14 @@
|
|||||||
exports[`CollapsibleSectionComponent renders 1`] = `
|
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Stack
|
<Stack
|
||||||
|
aria-expanded={true}
|
||||||
|
aria-name="Advanced"
|
||||||
className="collapsibleSection"
|
className="collapsibleSection"
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 10,
|
"childrenGap": 10,
|
||||||
|
|||||||
@@ -181,8 +181,7 @@ export const Dialog: FC = () => {
|
|||||||
text: secondaryButtonText,
|
text: secondaryButtonText,
|
||||||
onClick: onSecondaryButtonClick,
|
onClick: onSecondaryButtonClick,
|
||||||
}
|
}
|
||||||
: {};
|
: undefined;
|
||||||
|
|
||||||
return visible ? (
|
return visible ? (
|
||||||
<FluentDialog {...dialogProps}>
|
<FluentDialog {...dialogProps}>
|
||||||
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
.input-type-head-text-field {
|
.input-type-head-text-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.input-query-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|||||||
@@ -160,18 +160,21 @@ export class InputTypeaheadComponent extends React.Component<
|
|||||||
return (
|
return (
|
||||||
<div className="input-typeahead-container">
|
<div className="input-typeahead-container">
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<TextField
|
<form aria-labelledby="input" className="input-query-form">
|
||||||
multiline={useTextarea}
|
<TextField
|
||||||
rows={1}
|
multiline={useTextarea}
|
||||||
defaultValue={defaultValue}
|
rows={1}
|
||||||
ariaLabel="Input query"
|
id="input"
|
||||||
placeholder={placeholder}
|
defaultValue={defaultValue}
|
||||||
className="input-type-head-text-field"
|
ariaLabel="Input query"
|
||||||
value={defaultValue}
|
placeholder={placeholder}
|
||||||
onKeyDown={this.onSubmit}
|
className="input-type-head-text-field"
|
||||||
onFocus={() => this.setState({ isSuggestionVisible: true })}
|
value={defaultValue}
|
||||||
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
|
onKeyDown={this.onSubmit}
|
||||||
/>
|
onFocus={() => this.setState({ isSuggestionVisible: true })}
|
||||||
|
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
{this.props.showCancelButton && (
|
{this.props.showCancelButton && (
|
||||||
<IconButton
|
<IconButton
|
||||||
styles={iconButtonStyles}
|
styles={iconButtonStyles}
|
||||||
|
|||||||
@@ -7,16 +7,22 @@ exports[`inputTypeahead renders <input /> 1`] = `
|
|||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
>
|
>
|
||||||
<StyledTextFieldBase
|
<form
|
||||||
ariaLabel="Input query"
|
aria-labelledby="input"
|
||||||
className="input-type-head-text-field"
|
className="input-query-form"
|
||||||
multiline={false}
|
>
|
||||||
onChange={[Function]}
|
<StyledTextFieldBase
|
||||||
onFocus={[Function]}
|
ariaLabel="Input query"
|
||||||
onKeyDown={[Function]}
|
className="input-type-head-text-field"
|
||||||
placeholder="placeholder"
|
id="input"
|
||||||
rows={1}
|
multiline={false}
|
||||||
/>
|
onChange={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
placeholder="placeholder"
|
||||||
|
rows={1}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -28,16 +34,22 @@ exports[`inputTypeahead renders <textarea /> 1`] = `
|
|||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
>
|
>
|
||||||
<StyledTextFieldBase
|
<form
|
||||||
ariaLabel="Input query"
|
aria-labelledby="input"
|
||||||
className="input-type-head-text-field"
|
className="input-query-form"
|
||||||
multiline={true}
|
>
|
||||||
onChange={[Function]}
|
<StyledTextFieldBase
|
||||||
onFocus={[Function]}
|
ariaLabel="Input query"
|
||||||
onKeyDown={[Function]}
|
className="input-type-head-text-field"
|
||||||
placeholder="placeholder"
|
id="input"
|
||||||
rows={1}
|
multiline={true}
|
||||||
/>
|
onChange={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
placeholder="placeholder"
|
||||||
|
rows={1}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
import * as React from "react";
|
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
|
||||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
|
||||||
import {
|
import {
|
||||||
getPriceCurrency,
|
DetailsList,
|
||||||
getCurrencySign,
|
DetailsListLayoutMode,
|
||||||
getAutoscalePricePerRu,
|
DetailsRow,
|
||||||
getMultimasterMultiplier,
|
|
||||||
computeRUUsagePriceHourly,
|
|
||||||
getPricePerRu,
|
|
||||||
estimatedCostDisclaimer,
|
|
||||||
} from "../../../Utils/PricingUtils";
|
|
||||||
import {
|
|
||||||
ITextFieldStyles,
|
|
||||||
ICheckboxStyles,
|
ICheckboxStyles,
|
||||||
IStackProps,
|
|
||||||
IStackTokens,
|
|
||||||
IChoiceGroupStyles,
|
IChoiceGroupStyles,
|
||||||
Link,
|
IColumn,
|
||||||
Text,
|
IDetailsColumnStyles,
|
||||||
IMessageBarStyles,
|
|
||||||
ITextStyles,
|
|
||||||
IDetailsRowStyles,
|
|
||||||
IStackStyles,
|
|
||||||
IDetailsListStyles,
|
IDetailsListStyles,
|
||||||
|
IDetailsRowProps,
|
||||||
|
IDetailsRowStyles,
|
||||||
IDropdownStyles,
|
IDropdownStyles,
|
||||||
|
IMessageBarStyles,
|
||||||
ISeparatorStyles,
|
ISeparatorStyles,
|
||||||
|
IStackProps,
|
||||||
|
IStackStyles,
|
||||||
|
IStackTokens,
|
||||||
|
ITextFieldStyles,
|
||||||
|
ITextStyles,
|
||||||
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
Stack,
|
SelectionMode,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize,
|
SpinnerSize,
|
||||||
DetailsList,
|
Stack,
|
||||||
IColumn,
|
Text,
|
||||||
SelectionMode,
|
|
||||||
DetailsListLayoutMode,
|
|
||||||
IDetailsRowProps,
|
|
||||||
DetailsRow,
|
|
||||||
IDetailsColumnStyles,
|
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import * as React from "react";
|
||||||
|
import { StyleConstants, Urls } from "../../../Common/Constants";
|
||||||
|
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import {
|
||||||
|
computeRUUsagePriceHourly,
|
||||||
|
estimatedCostDisclaimer,
|
||||||
|
getAutoscalePricePerRu,
|
||||||
|
getCurrencySign,
|
||||||
|
getMultimasterMultiplier,
|
||||||
|
getPriceCurrency,
|
||||||
|
getPricePerRu,
|
||||||
|
} from "../../../Utils/PricingUtils";
|
||||||
|
import { isDirty, isDirtyTypes } from "./SettingsUtils";
|
||||||
|
|
||||||
export interface EstimatedSpendingDisplayProps {
|
export interface EstimatedSpendingDisplayProps {
|
||||||
costType: JSX.Element;
|
costType: JSX.Element;
|
||||||
@@ -65,7 +65,7 @@ export interface PriceBreakdown {
|
|||||||
currencySign: string;
|
currencySign: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
@@ -223,14 +223,15 @@ export const getRuPriceBreakdown = (
|
|||||||
multimasterEnabled: isMultimaster,
|
multimasterEnabled: isMultimaster,
|
||||||
isAutoscale: isAutoscale,
|
isAutoscale: isAutoscale,
|
||||||
});
|
});
|
||||||
const basePricePerRu: number = isAutoscale
|
const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster);
|
||||||
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
const pricePerRu: number = isAutoscale
|
||||||
: getPricePerRu(serverId);
|
? getAutoscalePricePerRu(serverId, multimasterMultiplier)
|
||||||
|
: getPricePerRu(serverId, multimasterMultiplier);
|
||||||
return {
|
return {
|
||||||
hourlyPrice: hourlyPrice,
|
hourlyPrice,
|
||||||
dailyPrice: hourlyPrice * 24,
|
dailyPrice: hourlyPrice * 24,
|
||||||
monthlyPrice: hourlyPrice * hoursInAMonth,
|
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||||
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
pricePerRu,
|
||||||
currency: getPriceCurrency(serverId),
|
currency: getPriceCurrency(serverId),
|
||||||
currencySign: getCurrencySign(serverId),
|
currencySign: getCurrencySign(serverId),
|
||||||
};
|
};
|
||||||
@@ -271,7 +272,7 @@ export const manualToAutoscaleDisclaimerElement: JSX.Element = (
|
|||||||
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
|
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
|
||||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
|
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
|
||||||
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
|
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
|
||||||
<a href={Urls.autoscaleMigration}>Learn more</a>
|
<Link href={Urls.autoscaleMigration}>Learn more</Link>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -39,6 +40,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -73,6 +75,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -80,11 +83,11 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
>
|
>
|
||||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
||||||
|
|
||||||
<a
|
<StyledLinkBase
|
||||||
href="https://aka.ms/cosmos-autoscale-migration"
|
href="https://aka.ms/cosmos-autoscale-migration"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</StyledLinkBase>
|
||||||
</Text>
|
</Text>
|
||||||
</StyledMessageBar>
|
</StyledMessageBar>
|
||||||
<StyledChoiceGroup
|
<StyledChoiceGroup
|
||||||
@@ -186,6 +189,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -460,6 +464,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -412,6 +413,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -952,6 +954,7 @@ exports[`SubSettingsComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1228,6 +1231,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
|
"phoenixClient": PhoenixClient {},
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -101,6 +102,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
|
"phoenixClient": PhoenixClient {},
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -166,16 +167,17 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
>
|
>
|
||||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
||||||
|
|
||||||
<a
|
<StyledLinkBase
|
||||||
href="https://aka.ms/cosmos-autoscale-migration"
|
href="https://aka.ms/cosmos-autoscale-migration"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</StyledLinkBase>
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -195,6 +197,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -207,6 +210,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -219,6 +223,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -230,6 +235,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -249,6 +255,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -264,6 +271,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -278,6 +286,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -291,6 +300,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -302,6 +312,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -321,6 +332,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -363,6 +375,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -378,6 +391,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -394,6 +408,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
@import "../../../../less/Common/Constants";
|
||||||
|
|
||||||
.tabComponentContainer {
|
.tabComponentContainer {
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
.flex-direction();
|
.flex-direction();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { userContext } from "../../../../UserContext";
|
|||||||
import {
|
import {
|
||||||
calculateEstimateNumber,
|
calculateEstimateNumber,
|
||||||
computeRUUsagePriceHourly,
|
computeRUUsagePriceHourly,
|
||||||
|
estimatedCostDisclaimer,
|
||||||
getAutoscalePricePerRu,
|
getAutoscalePricePerRu,
|
||||||
getCurrencySign,
|
getCurrencySign,
|
||||||
getMultimasterMultiplier,
|
getMultimasterMultiplier,
|
||||||
@@ -42,11 +43,9 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
|
|||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const pricePerRu = isAutoscale
|
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier);
|
||||||
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
|
||||||
: getPricePerRu(serverId) * multiplier;
|
|
||||||
|
|
||||||
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
|
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>{estimatedCostDisclaimer}</InfoTooltip>;
|
||||||
|
|
||||||
if (isAutoscale) {
|
if (isAutoscale) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
|
aria-required={true}
|
||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
@@ -131,6 +132,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
checked={!isAutoscaleSelected}
|
checked={!isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
aria-required={true}
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
|
|||||||
@@ -345,12 +345,14 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
|
tabIndex={0}
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -630,6 +632,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
className="panelInfoIcon root-57"
|
className="panelInfoIcon root-57"
|
||||||
data-icon-name="Info"
|
data-icon-name="Info"
|
||||||
role="img"
|
role="img"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
@@ -651,6 +654,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
|
aria-required={true}
|
||||||
checked={true}
|
checked={true}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
key=".0:$.0"
|
key=".0:$.0"
|
||||||
@@ -667,6 +671,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
|
aria-required={true}
|
||||||
checked={false}
|
checked={false}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
key=".0:$.2"
|
key=".0:$.2"
|
||||||
@@ -1327,12 +1332,14 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
|
tabIndex={0}
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -1612,6 +1619,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
className="panelInfoIcon root-57"
|
className="panelInfoIcon root-57"
|
||||||
data-icon-name="Info"
|
data-icon-name="Info"
|
||||||
role="img"
|
role="img"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
<div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}>
|
<div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}>
|
||||||
<IconButton
|
<IconButton
|
||||||
name="More"
|
name="More"
|
||||||
|
title="More"
|
||||||
className="treeMenuEllipsis"
|
className="treeMenuEllipsis"
|
||||||
ariaLabel={menuItemLabel}
|
ariaLabel={menuItemLabel}
|
||||||
menuIconProps={{
|
menuIconProps={{
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
title="More"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -423,6 +424,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
title="More"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
|||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
import { IGalleryItem } from "../Juno/JunoClient";
|
import { IGalleryItem } from "../Juno/JunoClient";
|
||||||
|
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
||||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -87,12 +88,13 @@ export default class Explorer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
private phoenixClient: PhoenixClient;
|
||||||
constructor() {
|
constructor() {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
|
this.phoenixClient = new PhoenixClient();
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
() => this.refreshCommandBarButtons(),
|
() => this.refreshCommandBarButtons(),
|
||||||
(state) => state.isNotebooksEnabledForAccount
|
(state) => state.isNotebooksEnabledForAccount
|
||||||
@@ -343,19 +345,36 @@ export default class Explorer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
|
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,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
|
||||||
await this.ensureNotebookWorkspaceRunning();
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
const connectionInfo = await listConnectionInfo(
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||||
userContext.subscriptionId,
|
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||||
userContext.resourceGroup,
|
});
|
||||||
databaseAccount.name,
|
}
|
||||||
"default"
|
|
||||||
);
|
|
||||||
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
||||||
|
|
||||||
@@ -364,7 +383,7 @@ export default class Explorer {
|
|||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetNotebookWorkspace() {
|
public resetNotebookWorkspace(): void {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||||
handleError(
|
handleError(
|
||||||
"Attempt to reset notebook workspace, but notebook is not enabled",
|
"Attempt to reset notebook workspace, but notebook is not enabled",
|
||||||
@@ -389,7 +408,6 @@ export default class Explorer {
|
|||||||
if (!databaseAccount) {
|
if (!databaseAccount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { value: workspaces } = await listByDatabaseAccount(
|
const { value: workspaces } = await listByDatabaseAccount(
|
||||||
userContext.subscriptionId,
|
userContext.subscriptionId,
|
||||||
@@ -906,7 +924,7 @@ export default class Explorer {
|
|||||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openNotebookTerminal(kind: ViewModels.TerminalKind) {
|
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||||
let title: string;
|
let title: string;
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
@@ -1026,7 +1044,10 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handleOpenFileAction(path: string): Promise<void> {
|
public async handleOpenFileAction(path: string): Promise<void> {
|
||||||
if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) {
|
if (
|
||||||
|
userContext.features.phoenix === false &&
|
||||||
|
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
||||||
|
) {
|
||||||
this._openSetupNotebooksPaneForQuickstart();
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1072,10 +1093,13 @@ export default class Explorer {
|
|||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases();
|
: this.refreshAllDatabases();
|
||||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||||
const isNotebookEnabled: boolean =
|
let isNotebookEnabled = true;
|
||||||
userContext.authType !== AuthType.ResourceToken &&
|
if (!userContext.features.phoenix) {
|
||||||
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
isNotebookEnabled =
|
||||||
userContext.features.enableNotebooks);
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
|
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||||
|
userContext.features.enableNotebooks);
|
||||||
|
}
|
||||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||||
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||||
|
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ export class D3ForceGraph implements GraphRenderer {
|
|||||||
.on("dblclick", function (this: Element, _: MouseEvent, d: D3Node) {
|
.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)
|
// https://stackoverflow.com/a/41945742 ('this' implicitly has type 'any' because it does not have a type annotation)
|
||||||
// this is the <g> element
|
// this is the <g> element
|
||||||
self.onNodeClicked(this.parentNode, d);
|
return self.onNodeClicked(this.parentNode, d);
|
||||||
})
|
})
|
||||||
.on("click", function (this: Element, _: MouseEvent, d: D3Node) {
|
.on("click", function (this: Element, _: MouseEvent, d: D3Node) {
|
||||||
// this is the <g> element
|
// this is the <g> element
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
|
|
||||||
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
|
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
|
||||||
import ExpandIcon from "../../../../images/Expand_14x14.svg";
|
import ExpandIcon from "../../../../images/Expand_14x14.svg";
|
||||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
|
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
|
||||||
|
|
||||||
interface MiddlePaneComponentProps {
|
interface MiddlePaneComponentProps {
|
||||||
isTabsContentExpanded: boolean;
|
isTabsContentExpanded: boolean;
|
||||||
@@ -17,7 +17,14 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
|
|||||||
<div className="middlePane">
|
<div className="middlePane">
|
||||||
<div className="graphTitle">
|
<div className="graphTitle">
|
||||||
<span className="paneTitle">Graph</span>
|
<span className="paneTitle">Graph</span>
|
||||||
<span className="graphExpandCollapseBtn pull-right" onClick={this.props.toggleExpandGraph}>
|
<span
|
||||||
|
className="graphExpandCollapseBtn pull-right"
|
||||||
|
onClick={this.props.toggleExpandGraph}
|
||||||
|
role="button"
|
||||||
|
aria-expanded={this.props.isTabsContentExpanded}
|
||||||
|
aria-name="View graph in full screen"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
||||||
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
|
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ import create, { UseStore } from "zustand";
|
|||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { useTabs } from "../../../hooks/useTabs";
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
@@ -54,6 +55,14 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
|
if (
|
||||||
|
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) {
|
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,9 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
notebookButtons.push(createOpenTerminalButton(container));
|
notebookButtons.push(createOpenTerminalButton(container));
|
||||||
|
if (userContext.features.phoenix === false) {
|
||||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
(userContext.apiType === "Mongo" &&
|
(userContext.apiType === "Mongo" &&
|
||||||
useNotebook.getState().isShellEnabled &&
|
useNotebook.getState().isShellEnabled &&
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { StyleConstants } from "../../../Common/Constants";
|
|||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
||||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,3 +202,10 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
|||||||
onRender: () => <MemoryTracker />,
|
onRender: () => <MemoryTracker />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createConnectionStatus = (key: string): ICommandBarItemProps => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
onRender: () => <ConnectionStatus />,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
79
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less
Normal file
79
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
@import "../../../../less/Common/Constants";
|
||||||
|
|
||||||
|
.connectionStatusContainer {
|
||||||
|
cursor: default;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 9px;
|
||||||
|
border: 1px;
|
||||||
|
min-height: 44px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding-right: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: @DataExplorerFont;
|
||||||
|
color: @DefaultFontColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.connectionStatusFailed{
|
||||||
|
color: #bd1919;
|
||||||
|
}
|
||||||
|
.ring-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ringringGreen {
|
||||||
|
border: 3px solid green;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
72
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
Normal file
72
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { ConnectionStatusType } from "../../../Common/Constants";
|
||||||
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
|
import "../CommandBar/ConnectionStatusComponent.less";
|
||||||
|
|
||||||
|
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("locationYellowDot");
|
||||||
|
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow");
|
||||||
|
const toolTipContent = "Hosted runtime status.";
|
||||||
|
React.useEffect(() => {
|
||||||
|
let intervalId: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
const secondCounter = counter % 60;
|
||||||
|
const minuteCounter = Math.floor(counter / 60);
|
||||||
|
const computedSecond: string = String(secondCounter).length === 1 ? `0${secondCounter}` : `${secondCounter}`;
|
||||||
|
const computedMinute: string = String(minuteCounter).length === 1 ? `0${minuteCounter}` : `${minuteCounter}`;
|
||||||
|
|
||||||
|
setSecond(computedSecond);
|
||||||
|
setMinute(computedMinute);
|
||||||
|
|
||||||
|
setCounter((counter) => counter + 1);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [isActive, counter]);
|
||||||
|
|
||||||
|
const stopTimer = () => {
|
||||||
|
setIsActive(false);
|
||||||
|
setCounter(0);
|
||||||
|
setSecond("00");
|
||||||
|
setMinute("00");
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||||
|
if (!connectionInfo) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||||
|
setIsActive(true);
|
||||||
|
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||||
|
stopTimer();
|
||||||
|
setStatusColor("locationGreenDot");
|
||||||
|
setStatusColorAnimation("ringringGreen");
|
||||||
|
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
||||||
|
stopTimer();
|
||||||
|
setStatusColor("locationRedDot");
|
||||||
|
setStatusColorAnimation("ringringRed");
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -129,7 +129,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label={"console button" + (this.props.isConsoleExpanded ? " collapsed" : " expanded")}
|
aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")}
|
||||||
aria-expanded={!this.props.isConsoleExpanded}
|
aria-expanded={!this.props.isConsoleExpanded}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -205,7 +205,9 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
{item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
|
{item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
|
||||||
{item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />}
|
{item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />}
|
||||||
<span className="date">{item.date}</span>
|
<span className="date">{item.date}</span>
|
||||||
<span className="message">{item.message}</span>
|
<span className="message" role="alert" aria-live="assertive">
|
||||||
|
{item.message}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-expanded={true}
|
aria-expanded={true}
|
||||||
aria-label="console button expanded"
|
aria-label="console button collapsed"
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -236,7 +236,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-expanded={true}
|
aria-expanded={true}
|
||||||
aria-label="console button expanded"
|
aria-label="console button collapsed"
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -340,7 +340,9 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
date
|
date
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
aria-live="assertive"
|
||||||
className="message"
|
className="message"
|
||||||
|
role="alert"
|
||||||
>
|
>
|
||||||
message
|
message
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
|
|||||||
const q = params.toString();
|
const q = params.toString();
|
||||||
const suffix = q !== "" ? `?${q}` : "";
|
const suffix = q !== "" ? `?${q}` : "";
|
||||||
|
|
||||||
const url = (serverConfig.endpoint || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
const url = (serverConfig.endpoint.slice(0, -1) || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
||||||
|
|
||||||
return url.replace(/^http(s)?/, "ws$1");
|
return url.replace(/^http(s)?/, "ws$1");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class NotebookContainerClient {
|
|||||||
|
|
||||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: authToken,
|
Authorization: authToken,
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ export class NotebookContentClient {
|
|||||||
*
|
*
|
||||||
* @param parent parent folder
|
* @param parent parent folder
|
||||||
*/
|
*/
|
||||||
public createNewNotebookFile(parent: NotebookContentItem, isGithubTree?: boolean): Promise<NotebookContentItem> {
|
public async createNewNotebookFile(
|
||||||
|
parent: NotebookContentItem,
|
||||||
|
isGithubTree?: boolean
|
||||||
|
): Promise<NotebookContentItem> {
|
||||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||||
throw new Error(`Parent must be a directory: ${parent}`);
|
throw new Error(`Parent must be a directory: ${parent}`);
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,6 @@ export class NotebookContentClient {
|
|||||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||||
throw new Error(`Parent must be a directory: ${parent}`);
|
throw new Error(`Parent must be a directory: ${parent}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filepath = NotebookUtil.getFilePath(parent.path, name);
|
const filepath = NotebookUtil.getFilePath(parent.path, name);
|
||||||
if (await this.checkIfFilepathExists(filepath)) {
|
if (await this.checkIfFilepathExists(filepath)) {
|
||||||
throw new Error(`File already exists: ${filepath}`);
|
throw new Error(`File already exists: ${filepath}`);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ interface NotebookState {
|
|||||||
myNotebooksContentRoot: NotebookContentItem;
|
myNotebooksContentRoot: NotebookContentItem;
|
||||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||||
galleryContentRoot: NotebookContentItem;
|
galleryContentRoot: NotebookContentItem;
|
||||||
|
connectionInfo: DataModels.ContainerConnectionInfo;
|
||||||
|
notebookFolderName: string;
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
@@ -36,6 +38,7 @@ interface NotebookState {
|
|||||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
|
||||||
setIsShellEnabled: (isShellEnabled: boolean) => void;
|
setIsShellEnabled: (isShellEnabled: boolean) => void;
|
||||||
setNotebookBasePath: (notebookBasePath: string) => void;
|
setNotebookBasePath: (notebookBasePath: string) => void;
|
||||||
|
setNotebookFolderName: (notebookFolderName: string) => void;
|
||||||
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
||||||
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
|
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
|
||||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
|
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||||
@@ -43,6 +46,7 @@ interface NotebookState {
|
|||||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||||
|
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
@@ -65,6 +69,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
myNotebooksContentRoot: undefined,
|
myNotebooksContentRoot: undefined,
|
||||||
gitHubNotebooksContentRoot: undefined,
|
gitHubNotebooksContentRoot: undefined,
|
||||||
galleryContentRoot: undefined,
|
galleryContentRoot: undefined,
|
||||||
|
connectionInfo: undefined,
|
||||||
|
notebookFolderName: undefined,
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
@@ -75,6 +81,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
||||||
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
||||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
|
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
const { databaseAccount, authType } = userContext;
|
const { databaseAccount, authType } = userContext;
|
||||||
if (
|
if (
|
||||||
@@ -168,8 +175,10 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
|
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks";
|
||||||
|
set({ notebookFolderName });
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: "My Notebooks",
|
name: get().notebookFolderName,
|
||||||
path: get().notebookBasePath,
|
path: get().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
@@ -185,6 +194,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
set({
|
set({
|
||||||
myNotebooksContentRoot,
|
myNotebooksContentRoot,
|
||||||
galleryContentRoot,
|
galleryContentRoot,
|
||||||
@@ -246,4 +256,5 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
set({ gitHubNotebooksContentRoot });
|
set({ gitHubNotebooksContentRoot });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
true
|
true
|
||||||
).toLocaleLowerCase()}.`}
|
).toLocaleLowerCase()}.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -210,6 +210,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
aria-label="New database id"
|
aria-label="New database id"
|
||||||
autoFocus
|
autoFocus
|
||||||
|
tabIndex={0}
|
||||||
value={this.state.newDatabaseId}
|
value={this.state.newDatabaseId}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
this.setState({ newDatabaseId: event.target.value })
|
this.setState({ newDatabaseId: event.target.value })
|
||||||
@@ -236,7 +237,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
true
|
true
|
||||||
).toLocaleLowerCase()} within the database.`}
|
).toLocaleLowerCase()} within the database.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@@ -279,7 +280,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -362,7 +363,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
|
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -409,7 +410,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content={this.getPartitionKeyTooltipText()}
|
content={this.getPartitionKeyTooltipText()}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -467,7 +468,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
||||||
billed in addition to the throughput amount you provisioned at the database level.`}
|
billed in addition to the throughput amount you provisioned at the database level.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@@ -497,7 +498,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
|
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
|
||||||
per partition key."
|
per partition key."
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -550,6 +551,72 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{this.shouldShowAnalyticalStoreOptions() && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<Stack horizontal>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Analytical store
|
||||||
|
</Text>
|
||||||
|
<TooltipHost
|
||||||
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
|
content={this.getAnalyticalStorageTooltipContent()}
|
||||||
|
>
|
||||||
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<input
|
||||||
|
className="panelRadioBtn"
|
||||||
|
checked={this.state.enableAnalyticalStore}
|
||||||
|
disabled={!this.isSynapseLinkEnabled()}
|
||||||
|
aria-label="Enable analytical store"
|
||||||
|
aria-checked={this.state.enableAnalyticalStore}
|
||||||
|
name="analyticalStore"
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
id="enableAnalyticalStoreBtn"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||||
|
/>
|
||||||
|
<span className="panelRadioBtnLabel">On</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="panelRadioBtn"
|
||||||
|
checked={!this.state.enableAnalyticalStore}
|
||||||
|
disabled={!this.isSynapseLinkEnabled()}
|
||||||
|
aria-label="Disable analytical store"
|
||||||
|
aria-checked={!this.state.enableAnalyticalStore}
|
||||||
|
name="analyticalStore"
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
id="disableAnalyticalStoreBtn"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||||
|
/>
|
||||||
|
<span className="panelRadioBtnLabel">Off</span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{!this.isSynapseLinkEnabled() && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<Text variant="small">
|
||||||
|
Azure Synapse Link is required for creating an analytical store{" "}
|
||||||
|
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account.{" "}
|
||||||
|
<Link href="https://aka.ms/cosmosdb-synapselink" target="_blank">
|
||||||
|
Learn more
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
<DefaultButton
|
||||||
|
text="Enable"
|
||||||
|
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
|
||||||
|
style={{ height: 27, width: 80 }}
|
||||||
|
styles={{ label: { fontSize: 12 } }}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
{userContext.apiType !== "Tables" && (
|
{userContext.apiType !== "Tables" && (
|
||||||
<CollapsibleSectionComponent
|
<CollapsibleSectionComponent
|
||||||
title="Advanced"
|
title="Advanced"
|
||||||
@@ -571,7 +638,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
|
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -604,72 +671,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.shouldShowAnalyticalStoreOptions() && (
|
|
||||||
<Stack className="panelGroupSpacing">
|
|
||||||
<Stack horizontal>
|
|
||||||
<Text className="panelTextBold" variant="small">
|
|
||||||
Analytical store
|
|
||||||
</Text>
|
|
||||||
<TooltipHost
|
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
content={this.getAnalyticalStorageTooltipContent()}
|
|
||||||
>
|
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
|
||||||
<input
|
|
||||||
className="panelRadioBtn"
|
|
||||||
checked={this.state.enableAnalyticalStore}
|
|
||||||
disabled={!this.isSynapseLinkEnabled()}
|
|
||||||
aria-label="Enable analytical store"
|
|
||||||
aria-checked={this.state.enableAnalyticalStore}
|
|
||||||
name="analyticalStore"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="enableAnalyticalStoreBtn"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="panelRadioBtnLabel">On</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className="panelRadioBtn"
|
|
||||||
checked={!this.state.enableAnalyticalStore}
|
|
||||||
disabled={!this.isSynapseLinkEnabled()}
|
|
||||||
aria-label="Disable analytical store"
|
|
||||||
aria-checked={!this.state.enableAnalyticalStore}
|
|
||||||
name="analyticalStore"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="disableAnalyticalStoreBtn"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="panelRadioBtnLabel">Off</span>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{!this.isSynapseLinkEnabled() && (
|
|
||||||
<Stack className="panelGroupSpacing">
|
|
||||||
<Text variant="small">
|
|
||||||
Azure Synapse Link is required for creating an analytical store{" "}
|
|
||||||
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account.{" "}
|
|
||||||
<Link href="https://aka.ms/cosmosdb-synapselink" target="_blank">
|
|
||||||
Learn more
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
<DefaultButton
|
|
||||||
text="Enable"
|
|
||||||
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
|
|
||||||
style={{ height: 27, width: 80 }}
|
|
||||||
styles={{ label: { fontSize: 12 } }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</CollapsibleSectionComponent>
|
</CollapsibleSectionComponent>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
|
|||||||
|
|
||||||
export interface AddDatabasePaneProps {
|
export interface AddDatabasePaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
|
buttonElement?: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||||
explorer: container,
|
explorer: container,
|
||||||
|
buttonElement,
|
||||||
}: AddDatabasePaneProps) => {
|
}: AddDatabasePaneProps) => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
let throughput: number;
|
let throughput: number;
|
||||||
@@ -77,6 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
};
|
};
|
||||||
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||||
|
buttonElement.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
<Stack className="panelGroupSpacing">
|
<Stack className="panelGroupSpacing">
|
||||||
<TextField
|
<TextField
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
|
required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
styles={getTextFieldStyles()}
|
styles={getTextFieldStyles()}
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
@@ -285,6 +286,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
underlined
|
underlined
|
||||||
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
|
required={true}
|
||||||
ariaLabel="addCollection-tableId"
|
ariaLabel="addCollection-tableId"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils
|
|||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -75,6 +76,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
selectedLocation.owner,
|
selectedLocation.owner,
|
||||||
selectedLocation.repo
|
selectedLocation.repo
|
||||||
)} - ${selectedLocation.branch}`;
|
)} - ${selectedLocation.branch}`;
|
||||||
|
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||||
|
destination = "My Notebooks Scratch";
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import React, { FormEvent, FunctionComponent } from "react";
|
import React, { FormEvent, FunctionComponent } from "react";
|
||||||
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
|
|
||||||
interface Location {
|
interface Location {
|
||||||
@@ -46,11 +47,10 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
|
|||||||
|
|
||||||
const getDropDownOptions = (): IDropdownOption[] => {
|
const getDropDownOptions = (): IDropdownOption[] => {
|
||||||
const options: IDropdownOption[] = [];
|
const options: IDropdownOption[] = [];
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
key: "MyNotebooks-Item",
|
key: "MyNotebooks-Item",
|
||||||
text: ResourceTreeAdapter.MyNotebooksTitle,
|
text: useNotebook.getState().notebookFolderName,
|
||||||
title: ResourceTreeAdapter.MyNotebooksTitle,
|
title: useNotebook.getState().notebookFolderName,
|
||||||
data: {
|
data: {
|
||||||
type: "MyNotebooks",
|
type: "MyNotebooks",
|
||||||
} as Location,
|
} as Location,
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
const confirmContainer = `Confirm by typing the ${collectionName.toLowerCase()} id`;
|
||||||
|
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${collectionName}?`;
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
@@ -123,6 +125,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setInputCollectionName(newInput);
|
setInputCollectionName(newInput);
|
||||||
}}
|
}}
|
||||||
|
ariaLabel={confirmContainer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{shouldRecordFeedback() && (
|
{shouldRecordFeedback() && (
|
||||||
@@ -142,6 +145,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDeleteCollectionFeedback(newInput);
|
setDeleteCollectionFeedback(newInput);
|
||||||
}}
|
}}
|
||||||
|
ariaLabel={reasonInfo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Confirm by typing the container id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -53,6 +54,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
|
ariaLabel="Confirm by typing the container id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
@@ -346,6 +348,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
|
aria-label="Confirm by typing the container id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-57"
|
className="ms-TextField-field field-57"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
message:
|
message:
|
||||||
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||||
};
|
};
|
||||||
|
const confirmDatabase = "Confirm by typing the database id";
|
||||||
|
const reasonInfo = "Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?";
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||||
@@ -133,6 +134,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDatabaseInput(newInput);
|
setDatabaseInput(newInput);
|
||||||
}}
|
}}
|
||||||
|
ariaLabel={confirmDatabase}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isLastNonEmptyDatabase() && (
|
{isLastNonEmptyDatabase() && (
|
||||||
@@ -151,6 +153,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDatabaseFeedbackInput(newInput);
|
setDatabaseFeedbackInput(newInput);
|
||||||
}}
|
}}
|
||||||
|
ariaLabel={reasonInfo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
selectedKey={paramKeyValue && paramKeyValue.key}
|
selectedKey={paramKeyValue && paramKeyValue.key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Stack horizontal onClick={addNewParamAtLastIndex}>
|
<Stack horizontal onClick={addNewParamAtLastIndex} tabIndex={0}>
|
||||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
||||||
<Text className="addNewParamStyle">Add New Param</Text>
|
<Text className="addNewParamStyle">Add New Param</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -59,30 +59,36 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
onChange={onParamKeyChange}
|
onChange={onParamKeyChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label={inputLabel && inputLabel}
|
label={inputLabel && inputLabel}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
autoFocus
|
|
||||||
value={paramValue}
|
value={paramValue}
|
||||||
onChange={onParamValueChange}
|
onChange={onParamValueChange}
|
||||||
/>
|
/>
|
||||||
{isAddRemoveVisible && (
|
{isAddRemoveVisible && (
|
||||||
<>
|
<>
|
||||||
<Image
|
<div tabIndex={0}>
|
||||||
{...imageProps}
|
<Image
|
||||||
src={EntityCancelIcon}
|
{...imageProps}
|
||||||
alt="Delete param"
|
src={EntityCancelIcon}
|
||||||
id="deleteparam"
|
alt="Delete param"
|
||||||
onClick={onDeleteParamKeyPress}
|
id="deleteparam"
|
||||||
/>
|
role="button"
|
||||||
<Image
|
onClick={onDeleteParamKeyPress}
|
||||||
{...imageProps}
|
/>
|
||||||
src={AddPropertyIcon}
|
</div>
|
||||||
alt="Add param"
|
<div tabIndex={0}>
|
||||||
id="addparam"
|
<Image
|
||||||
onClick={onAddNewParamKeyPress}
|
{...imageProps}
|
||||||
/>
|
src={AddPropertyIcon}
|
||||||
|
alt="Add param"
|
||||||
|
id="addparam"
|
||||||
|
role="button"
|
||||||
|
onClick={onAddNewParamKeyPress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
|
"phoenixClient": PhoenixClient {},
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
.panelFormWrapper {
|
.panelFormWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
min-height: 100%;
|
||||||
|
|
||||||
.panelMainContent {
|
.panelMainContent {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0 34px;
|
padding: 0 34px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
overflow: auto;
|
overflow-x: hidden;
|
||||||
|
|
||||||
& > :not(.collapsibleSection) {
|
& > :not(.collapsibleSection) {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
|||||||
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
|
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
|
||||||
{icon}
|
{icon}
|
||||||
<span className="panelWarningErrorDetailsLinkContainer">
|
<span className="panelWarningErrorDetailsLinkContainer">
|
||||||
<Text className="panelWarningErrorMessage" variant="small" aria-label="message">
|
<Text
|
||||||
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-label={message}
|
||||||
|
className="panelWarningErrorMessage"
|
||||||
|
variant="small"
|
||||||
|
>
|
||||||
{message}
|
{message}
|
||||||
{link && linkText && (
|
{link && linkText && (
|
||||||
<Link target="_blank" href={link}>
|
<Link target="_blank" href={link}>
|
||||||
|
|||||||
@@ -34,6 +34,6 @@ describe("Right Pane Form", () => {
|
|||||||
it("should render error in header", () => {
|
it("should render error in header", () => {
|
||||||
render(<RightPaneForm {...props} formError="file already Exist" />);
|
render(<RightPaneForm {...props} formError="file already Exist" />);
|
||||||
expect(screen.getByLabelText("error")).toBeDefined();
|
expect(screen.getByLabelText("error")).toBeDefined();
|
||||||
expect(screen.getByLabelText("message").innerHTML).toEqual("file already Exist");
|
expect(screen.getByRole("alert").innerHTML).toEqual("file already Exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ export const SettingsPane: FunctionComponent = () => {
|
|||||||
step={1}
|
step={1}
|
||||||
className="textfontclr"
|
className="textfontclr"
|
||||||
role="textbox"
|
role="textbox"
|
||||||
tabIndex={0}
|
|
||||||
id="max-degree"
|
id="max-degree"
|
||||||
value={"" + maxDegreeOfParallelism}
|
value={"" + maxDegreeOfParallelism}
|
||||||
onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)}
|
onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)}
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
onValidate={[Function]}
|
onValidate={[Function]}
|
||||||
role="textbox"
|
role="textbox"
|
||||||
step={1}
|
step={1}
|
||||||
tabIndex={0}
|
|
||||||
value="6"
|
value="6"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
|
"phoenixClient": PhoenixClient {},
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React from "react";
|
|||||||
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
import * as Entities from "../../Tables/Entities";
|
import * as Entities from "../../Tables/Entities";
|
||||||
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||||
import { AddTableEntityPanel } from "./AddTableEntityPanel";
|
import { AddTableEntityPanel } from "./AddTableEntityPanel";
|
||||||
|
|
||||||
describe("Excute Add Table Entity Pane", () => {
|
describe("Excute Add Table Entity Pane", () => {
|
||||||
@@ -18,6 +18,8 @@ describe("Excute Add Table Entity Pane", () => {
|
|||||||
queryTablesTab: fakeQueryTablesTab,
|
queryTablesTab: fakeQueryTablesTab,
|
||||||
tableEntityListViewModel: fakeTableEntityListViewModel,
|
tableEntityListViewModel: fakeTableEntityListViewModel,
|
||||||
cassandraApiClient: fakeCassandraApiClient,
|
cassandraApiClient: fakeCassandraApiClient,
|
||||||
|
reloadEntities: () => "{}",
|
||||||
|
headerItems: ["email"],
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should render Default properly", () => {
|
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", () => {
|
it("initially display 4 input field, 2 properties and 2 entity values", () => {
|
||||||
const wrapper = mount(<AddTableEntityPanel {...props} />);
|
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", () => {
|
it("add a new entity row", () => {
|
||||||
const wrapper = mount(<AddTableEntityPanel {...props} />);
|
const wrapper = mount(<AddTableEntityPanel {...props} />);
|
||||||
wrapper.find(".addButtonEntiy").last().simulate("click");
|
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", () => {
|
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.
|
// 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(".addButtonEntiy").last().simulate("click");
|
||||||
wrapper.find("#deleteEntity").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 { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as _ from "underscore";
|
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { TableEntity } from "../../../Common/TableEntity";
|
import { TableEntity } from "../../../Common/TableEntity";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as TableConstants from "../../Tables/Constants";
|
import * as TableConstants from "../../Tables/Constants";
|
||||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||||
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
import * as Entities from "../../Tables/Entities";
|
import * as Entities from "../../Tables/Entities";
|
||||||
import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient";
|
||||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
|
||||||
import * as Utilities from "../../Tables/Utilities";
|
import * as Utilities from "../../Tables/Utilities";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import {
|
import {
|
||||||
attributeNameLabel,
|
attributeNameLabel,
|
||||||
@@ -36,9 +35,11 @@ import {
|
|||||||
|
|
||||||
interface AddTableEntityPanelProps {
|
interface AddTableEntityPanelProps {
|
||||||
tableDataClient: TableDataClient;
|
tableDataClient: TableDataClient;
|
||||||
queryTablesTab: QueryTablesTab;
|
queryTablesTab: NewQueryTablesTab;
|
||||||
tableEntityListViewModel: TableEntityListViewModel;
|
tableEntityListViewModel: TableEntityListViewModel;
|
||||||
cassandraApiClient: CassandraAPIDataClient;
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
|
reloadEntities: () => void;
|
||||||
|
headerItems: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EntityRowType {
|
interface EntityRowType {
|
||||||
@@ -58,7 +59,10 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
queryTablesTab,
|
queryTablesTab,
|
||||||
tableEntityListViewModel,
|
tableEntityListViewModel,
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
|
reloadEntities,
|
||||||
|
headerItems,
|
||||||
}: AddTableEntityPanelProps): JSX.Element => {
|
}: AddTableEntityPanelProps): JSX.Element => {
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||||
@@ -76,7 +80,7 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getDefaultEntitiesAttribute = async (): Promise<void> => {
|
const getDefaultEntitiesAttribute = async (): Promise<void> => {
|
||||||
let headers = tableEntityListViewModel.headers;
|
let headers = tableEntityListViewModel.headers?.length > 1 ? tableEntityListViewModel.headers : headerItems;
|
||||||
if (DataTableUtilities.checkForDefaultHeader(headers)) {
|
if (DataTableUtilities.checkForDefaultHeader(headers)) {
|
||||||
headers = [];
|
headers = [];
|
||||||
if (userContext.apiType === "Tables") {
|
if (userContext.apiType === "Tables") {
|
||||||
@@ -116,47 +120,19 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
||||||
try {
|
try {
|
||||||
await tableEntityListViewModel.addEntityToCache(newEntity);
|
await tableEntityListViewModel.addEntityToCache(newEntity);
|
||||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
reloadEntities();
|
||||||
tableEntityListViewModel.redrawTableThrottled();
|
setFormError("");
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
setFormError(errorMessage);
|
setFormError(errorMessage);
|
||||||
handleError(errorMessage, "AddTableRow");
|
handleError(errorMessage, "AddTableRow");
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
closeSidePanel();
|
||||||
setIsExecuting(false);
|
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 */
|
/* Add new entity row */
|
||||||
const addNewEntity = (): void => {
|
const addNewEntity = (): void => {
|
||||||
const cloneEntities: EntityRowType[] = [...entities];
|
const cloneEntities: EntityRowType[] = [...entities];
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React from "react";
|
|||||||
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
import * as Entities from "../../Tables/Entities";
|
import * as Entities from "../../Tables/Entities";
|
||||||
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||||
import { EditTableEntityPanel } from "./EditTableEntityPanel";
|
import { EditTableEntityPanel } from "./EditTableEntityPanel";
|
||||||
|
|
||||||
describe("Excute Edit Table Entity Pane", () => {
|
describe("Excute Edit Table Entity Pane", () => {
|
||||||
@@ -15,11 +15,14 @@ describe("Excute Edit Table Entity Pane", () => {
|
|||||||
fakeTableEntityListViewModel.headers = [];
|
fakeTableEntityListViewModel.headers = [];
|
||||||
fakeTableEntityListViewModel.selected = ko.observableArray<Entities.ITableEntity>([{}]);
|
fakeTableEntityListViewModel.selected = ko.observableArray<Entities.ITableEntity>([{}]);
|
||||||
|
|
||||||
|
const fakeSelectedItem = [{ PartitionKey: { _: "test", $: "String" } }];
|
||||||
const props = {
|
const props = {
|
||||||
tableDataClient: new TablesAPIDataClient(),
|
tableDataClient: new TablesAPIDataClient(),
|
||||||
queryTablesTab: fakeQueryTablesTab,
|
queryTablesTab: fakeQueryTablesTab,
|
||||||
tableEntityListViewModel: fakeTableEntityListViewModel,
|
tableEntityListViewModel: fakeTableEntityListViewModel,
|
||||||
cassandraApiClient: fakeCassandraApiClient,
|
cassandraApiClient: fakeCassandraApiClient,
|
||||||
|
selectedEntity: fakeSelectedItem,
|
||||||
|
reloadEntities: () => "{}",
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should render Default properly", () => {
|
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", () => {
|
it("initially display 4 input field, 2 properties and 1 entity values", () => {
|
||||||
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
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", () => {
|
it("add a new entity row", () => {
|
||||||
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||||
wrapper.find(".addButtonEntiy").last().simulate("click");
|
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", () => {
|
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.
|
// 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(".addButtonEntiy").last().simulate("click");
|
||||||
wrapper.find("#deleteEntity").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 RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { TableEntity } from "../../../Common/TableEntity";
|
import { TableEntity } from "../../../Common/TableEntity";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as TableConstants from "../../Tables/Constants";
|
import * as TableConstants from "../../Tables/Constants";
|
||||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
|
||||||
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
import * as Entities from "../../Tables/Entities";
|
import * as Entities from "../../Tables/Entities";
|
||||||
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
|
||||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import {
|
import {
|
||||||
attributeNameLabel,
|
attributeNameLabel,
|
||||||
@@ -34,9 +34,11 @@ import {
|
|||||||
|
|
||||||
interface EditTableEntityPanelProps {
|
interface EditTableEntityPanelProps {
|
||||||
tableDataClient: TableDataClient;
|
tableDataClient: TableDataClient;
|
||||||
queryTablesTab: QueryTablesTab;
|
queryTablesTab: NewQueryTablesTab;
|
||||||
tableEntityListViewModel: TableEntityListViewModel;
|
tableEntityListViewModel: TableEntityListViewModel;
|
||||||
cassandraApiClient: CassandraAPIDataClient;
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
|
selectedEntity: Entities.ITableEntity[];
|
||||||
|
reloadEntities: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EntityRowType {
|
interface EntityRowType {
|
||||||
@@ -57,7 +59,10 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
queryTablesTab,
|
queryTablesTab,
|
||||||
tableEntityListViewModel,
|
tableEntityListViewModel,
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
|
selectedEntity,
|
||||||
|
reloadEntities,
|
||||||
}: EditTableEntityPanelProps): JSX.Element => {
|
}: EditTableEntityPanelProps): JSX.Element => {
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||||
@@ -75,8 +80,8 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let originalDocument: { [key: string]: any } = {};
|
let originalDocument: { [key: string]: any } = {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const entityAttribute: any = tableEntityListViewModel.selected();
|
const entityAttribute: any = selectedEntity;
|
||||||
const entityFormattedAttribute = constructDisplayedAttributes(entityAttribute[0]);
|
const entityFormattedAttribute = constructDisplayedAttributes(entityAttribute && entityAttribute[0]);
|
||||||
setEntities(entityFormattedAttribute);
|
setEntities(entityFormattedAttribute);
|
||||||
|
|
||||||
if (userContext.apiType === "Tables") {
|
if (userContext.apiType === "Tables") {
|
||||||
@@ -86,6 +91,7 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
originalDocument = entityAttribute;
|
originalDocument = entityAttribute;
|
||||||
}
|
}
|
||||||
setOriginalDocument(originalDocument);
|
setOriginalDocument(originalDocument);
|
||||||
|
//eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const constructDisplayedAttributes = (entity: Entities.ITableEntity): EntityRowType[] => {
|
const constructDisplayedAttributes = (entity: Entities.ITableEntity): EntityRowType[] => {
|
||||||
@@ -216,9 +222,8 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
entity
|
entity
|
||||||
);
|
);
|
||||||
await tableEntityListViewModel.updateCachedEntity(newEntity);
|
await tableEntityListViewModel.updateCachedEntity(newEntity);
|
||||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
reloadEntities();
|
||||||
tableEntityListViewModel.redrawTableThrottled();
|
closeSidePanel();
|
||||||
}
|
|
||||||
tableEntityListViewModel.selected.removeAll();
|
tableEntityListViewModel.selected.removeAll();
|
||||||
tableEntityListViewModel.selected.push(newEntity);
|
tableEntityListViewModel.selected.push(newEntity);
|
||||||
} catch (error) {
|
} 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
|
// Add new entity row
|
||||||
const addNewEntity = (): void => {
|
const addNewEntity = (): void => {
|
||||||
const cloneEntities = [...entities];
|
const cloneEntities = [...entities];
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Explorer from "../../../Explorer";
|
|
||||||
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
||||||
import { TableQuerySelectPanel } from "./TableQuerySelectPanel";
|
import { TableQuerySelectPanel } from "./TableQuerySelectPanel";
|
||||||
|
|
||||||
describe("Table query select Panel", () => {
|
describe("Table query select Panel", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
|
||||||
const fakeQueryViewModal = {} as QueryViewModel;
|
const fakeQueryViewModal = {} as QueryViewModel;
|
||||||
fakeQueryViewModal.columnOptions = ko.observableArray<string>([""]);
|
fakeQueryViewModal.columnOptions = ko.observableArray<string>([""]);
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
explorer: fakeExplorer,
|
headers: [""],
|
||||||
closePanel: (): void => undefined,
|
getSelectMessage: () => "{}",
|
||||||
queryViewModel: fakeQueryViewModal,
|
queryViewModel: fakeQueryViewModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,6 +27,7 @@ describe("Table query select Panel", () => {
|
|||||||
it("Should checked availableCheckbox by default", () => {
|
it("Should checked availableCheckbox by default", () => {
|
||||||
const wrapper = mount(<TableQuerySelectPanel {...props} />);
|
const wrapper = mount(<TableQuerySelectPanel {...props} />);
|
||||||
expect(wrapper.find("#availableCheckbox").first().props()).toEqual({
|
expect(wrapper.find("#availableCheckbox").first().props()).toEqual({
|
||||||
|
ariaPositionInSet: 0,
|
||||||
id: "availableCheckbox",
|
id: "availableCheckbox",
|
||||||
label: "Available Columns",
|
label: "Available Columns",
|
||||||
checked: true,
|
checked: true,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { RightPaneForm, RightPaneFormProps } from "../../RightPaneForm/RightPane
|
|||||||
|
|
||||||
interface TableQuerySelectPanelProps {
|
interface TableQuerySelectPanelProps {
|
||||||
queryViewModel: QueryViewModel;
|
queryViewModel: QueryViewModel;
|
||||||
|
headers: string[];
|
||||||
|
getSelectMessage: (selectMessage: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISelectColumn {
|
interface ISelectColumn {
|
||||||
@@ -18,6 +20,8 @@ interface ISelectColumn {
|
|||||||
|
|
||||||
export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps> = ({
|
export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps> = ({
|
||||||
queryViewModel,
|
queryViewModel,
|
||||||
|
headers,
|
||||||
|
getSelectMessage,
|
||||||
}: TableQuerySelectPanelProps): JSX.Element => {
|
}: TableQuerySelectPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
|
||||||
@@ -29,6 +33,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
const onSubmit = (): void => {
|
const onSubmit = (): void => {
|
||||||
queryViewModel.selectText(getParameters());
|
queryViewModel.selectText(getParameters());
|
||||||
queryViewModel.getSelectMessage();
|
queryViewModel.getSelectMessage();
|
||||||
|
getSelectMessage(queryViewModel.selectMessage());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +57,8 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryViewModel && setTableColumns(queryViewModel.columnOptions());
|
// queryViewModel && setTableColumns(queryViewModel.columnOptions());
|
||||||
|
headers && setTableColumns(headers);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setTableColumns = (columnNames: string[]): void => {
|
const setTableColumns = (columnNames: string[]): void => {
|
||||||
@@ -128,8 +134,9 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
label="Available Columns"
|
label="Available Columns"
|
||||||
checked={isAvailableColumnChecked}
|
checked={isAvailableColumnChecked}
|
||||||
onChange={availableColumnsCheckboxClick}
|
onChange={availableColumnsCheckboxClick}
|
||||||
|
ariaPositionInSet={0}
|
||||||
/>
|
/>
|
||||||
{columnOptions.map((column) => {
|
{columnOptions.map((column, index) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={column.columnName}
|
label={column.columnName}
|
||||||
@@ -137,6 +144,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
key={column.columnName}
|
key={column.columnName}
|
||||||
checked={column.selected}
|
checked={column.selected}
|
||||||
disabled={!column.editable}
|
disabled={!column.editable}
|
||||||
|
ariaPositionInSet={index + 1}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
exports[`Table query select Panel should render Default properly 1`] = `
|
exports[`Table query select Panel should render Default properly 1`] = `
|
||||||
<TableQuerySelectPanel
|
<TableQuerySelectPanel
|
||||||
closePanel={[Function]}
|
getSelectMessage={[Function]}
|
||||||
explorer={Object {}}
|
headers={
|
||||||
|
Array [
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
}
|
||||||
queryViewModel={
|
queryViewModel={
|
||||||
Object {
|
Object {
|
||||||
"columnOptions": [Function],
|
"columnOptions": [Function],
|
||||||
@@ -37,12 +41,14 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
className="column-select-view"
|
className="column-select-view"
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
|
ariaPositionInSet={0}
|
||||||
checked={true}
|
checked={true}
|
||||||
id="availableCheckbox"
|
id="availableCheckbox"
|
||||||
label="Available Columns"
|
label="Available Columns"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
>
|
>
|
||||||
<CheckboxBase
|
<CheckboxBase
|
||||||
|
ariaPositionInSet={0}
|
||||||
checked={true}
|
checked={true}
|
||||||
id="availableCheckbox"
|
id="availableCheckbox"
|
||||||
label="Available Columns"
|
label="Available Columns"
|
||||||
@@ -328,6 +334,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-checked="true"
|
aria-checked="true"
|
||||||
aria-label="Available Columns"
|
aria-label="Available Columns"
|
||||||
|
aria-posinset={0}
|
||||||
checked={true}
|
checked={true}
|
||||||
className="input-55"
|
className="input-55"
|
||||||
data-ktp-execute-target={true}
|
data-ktp-execute-target={true}
|
||||||
@@ -646,6 +653,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
</CheckboxBase>
|
</CheckboxBase>
|
||||||
</StyledCheckboxBase>
|
</StyledCheckboxBase>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
|
ariaPositionInSet={1}
|
||||||
checked={true}
|
checked={true}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
key=""
|
key=""
|
||||||
@@ -653,6 +661,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
>
|
>
|
||||||
<CheckboxBase
|
<CheckboxBase
|
||||||
|
ariaPositionInSet={1}
|
||||||
checked={true}
|
checked={true}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
label=""
|
label=""
|
||||||
@@ -939,6 +948,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
aria-checked="true"
|
aria-checked="true"
|
||||||
aria-disabled={false}
|
aria-disabled={false}
|
||||||
aria-label=""
|
aria-label=""
|
||||||
|
aria-posinset={1}
|
||||||
checked={true}
|
checked={true}
|
||||||
className="input-55"
|
className="input-55"
|
||||||
data-ktp-execute-target={true}
|
data-ktp-execute-target={true}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -327,13 +327,17 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
key=".0:$.1"
|
key=".0:$.1"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
aria-label="message"
|
aria-label="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources."
|
||||||
|
aria-live="assertive"
|
||||||
className="panelWarningErrorMessage"
|
className="panelWarningErrorMessage"
|
||||||
|
role="alert"
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="message"
|
aria-label="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources."
|
||||||
|
aria-live="assertive"
|
||||||
className="panelWarningErrorMessage css-56"
|
className="panelWarningErrorMessage css-56"
|
||||||
|
role="alert"
|
||||||
>
|
>
|
||||||
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
||||||
</span>
|
</span>
|
||||||
@@ -363,6 +367,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Confirm by typing the database id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -375,6 +380,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
|
ariaLabel="Confirm by typing the database id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
@@ -667,6 +673,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
|
aria-label="Confirm by typing the database id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-60"
|
className="ms-TextField-field field-60"
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
@@ -707,6 +714,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -720,6 +728,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
|
ariaLabel="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -1013,6 +1022,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
|
aria-label="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
|
||||||
className="ms-TextField-field field-71"
|
className="ms-TextField-field field-71"
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
|
|||||||
@@ -307,16 +307,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
title: "New " + getDatabaseName(),
|
title: "New " + getDatabaseName(),
|
||||||
description: undefined,
|
description: undefined,
|
||||||
onClick: () =>
|
onClick: () => this.openAddDatabasePanel(),
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private openAddDatabasePanel() {
|
||||||
|
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"New " + getDatabaseName(),
|
||||||
|
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||||
return {
|
return {
|
||||||
iconSrc: NotebookIcon,
|
iconSrc: NotebookIcon,
|
||||||
|
|||||||
@@ -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,192 +0,0 @@
|
|||||||
import _ from "underscore";
|
|
||||||
import Q from "q";
|
|
||||||
|
|
||||||
import * as Entities from "../Entities";
|
|
||||||
import * as QueryBuilderConstants from "../Constants";
|
|
||||||
import * as Utilities from "../Utilities";
|
|
||||||
|
|
||||||
export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
|
|
||||||
var selector: string = "";
|
|
||||||
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 {
|
|
||||||
var isVisible = false;
|
|
||||||
|
|
||||||
if (dataTableScrollBodyQuery.length && element) {
|
|
||||||
var 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) {
|
|
||||||
var dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector),
|
|
||||||
selectedRowElement: HTMLElement = dataTableRows.get(currentIndex);
|
|
||||||
|
|
||||||
if (dataTableScrollBodyQuery.length && selectedRowElement) {
|
|
||||||
var isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement);
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
var selectedRowQuery: JQuery = $(selectedRowElement),
|
|
||||||
scrollPosition: number = dataTableScrollBodyQuery.scrollTop(),
|
|
||||||
selectedElementPosition: number = selectedRowQuery.position().top,
|
|
||||||
newScrollPosition: number = 0;
|
|
||||||
|
|
||||||
if (isScrollUp) {
|
|
||||||
newScrollPosition = scrollPosition + selectedElementPosition;
|
|
||||||
} else {
|
|
||||||
newScrollPosition =
|
|
||||||
scrollPosition + (selectedElementPosition + selectedRowQuery.height() - dataTableScrollBodyQuery.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
dataTableScrollBodyQuery.scrollTop(newScrollPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function scrollToTopIfNeeded(): void {
|
|
||||||
var $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[]
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var columnsCount: number = targetOrder.length;
|
|
||||||
var isCurrentOrderPassedIn: boolean = !!currentOrder;
|
|
||||||
if (!isCurrentOrderPassedIn) {
|
|
||||||
currentOrder = getInitialOrder(columnsCount);
|
|
||||||
}
|
|
||||||
var 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.
|
|
||||||
var 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[] {
|
|
||||||
var 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[] {
|
|
||||||
var transformationOrder: number[] = [];
|
|
||||||
if (currentOrder && targetOrder && currentOrder.length === targetOrder.length) {
|
|
||||||
var invertedCurrentOrder: number[] = invertIndexValues(currentOrder);
|
|
||||||
transformationOrder = targetOrder.map((value: number) => invertedCurrentOrder[value]);
|
|
||||||
}
|
|
||||||
return transformationOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDataTableHeaders(table: DataTables.DataTable): string[] {
|
|
||||||
var columns: DataTables.ColumnsMethods = table.columns();
|
|
||||||
var headers: string[] = [];
|
|
||||||
if (columns) {
|
|
||||||
// table.columns() return ColumnsMethods which is an array of arrays
|
|
||||||
var 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 {
|
export function forceRecalculateTableSize(): void {
|
||||||
$("body").trigger("resize");
|
$("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 ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import CacheBase from "./CacheBase";
|
|
||||||
import * as CommonConstants from "../../../Common/Constants";
|
import * as CommonConstants from "../../../Common/Constants";
|
||||||
import * as Constants from "../Constants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as Entities from "../Entities";
|
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
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,
|
// 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.
|
// and property names are defined by Datatable as well.
|
||||||
@@ -47,19 +45,11 @@ abstract class DataTableViewModel {
|
|||||||
private pendingRedraw = false;
|
private pendingRedraw = false;
|
||||||
private lastRedrawTime = new Date().getTime();
|
private lastRedrawTime = new Date().getTime();
|
||||||
|
|
||||||
private dataTableOperationManager: IDataTableOperation;
|
public queryTablesTab: NewQueryTablesTab;
|
||||||
|
|
||||||
public queryTablesTab: QueryTablesTab;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.items([]);
|
this.items([]);
|
||||||
this.selected([]);
|
this.selected([]);
|
||||||
// Late bound
|
|
||||||
this.dataTableOperationManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bind(dataTableOperationManager: IDataTableOperation): void {
|
|
||||||
this.dataTableOperationManager = dataTableOperationManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearLastSelected(): void {
|
public clearLastSelected(): void {
|
||||||
@@ -101,10 +91,6 @@ abstract class DataTableViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public focusDataTable(): void {
|
|
||||||
this.dataTableOperationManager.focusTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getItemFromSelectedItems(itemKeys: Entities.IProperty[]): Entities.ITableEntity {
|
public getItemFromSelectedItems(itemKeys: Entities.IProperty[]): Entities.ITableEntity {
|
||||||
return _.find(this.selected(), (item: Entities.ITableEntity) => {
|
return _.find(this.selected(), (item: Entities.ITableEntity) => {
|
||||||
return this.matchesKeys(item, itemKeys);
|
return this.matchesKeys(item, itemKeys);
|
||||||
@@ -170,35 +156,12 @@ abstract class DataTableViewModel {
|
|||||||
this.cache.sortOrder = sortOrder;
|
this.cache.sortOrder = sortOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderPage(
|
protected renderPage(startIndex: number, pageSize: number) {
|
||||||
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
|
|
||||||
var endIndex = pageSize < 0 ? this.cache.length : startIndex + pageSize;
|
var endIndex = pageSize < 0 ? this.cache.length : startIndex + pageSize;
|
||||||
var renderData = this.cache.data.slice(startIndex, endIndex);
|
var renderData = this.cache.data.slice(startIndex, endIndex);
|
||||||
|
|
||||||
this.items(renderData);
|
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) {
|
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.Tab,
|
Action.Tab,
|
||||||
@@ -217,16 +180,6 @@ abstract class DataTableViewModel {
|
|||||||
protected matchesKeys(item: Entities.ITableEntity, itemKeys: Entities.IProperty[]): boolean {
|
protected matchesKeys(item: Entities.ITableEntity, itemKeys: Entities.IProperty[]): boolean {
|
||||||
return itemKeys.every((property: Entities.IProperty) => {
|
return itemKeys.every((property: Entities.IProperty) => {
|
||||||
var itemValue = item[property.key];
|
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);
|
return this.stringCompare(itemValue._, property.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -238,27 +191,6 @@ abstract class DataTableViewModel {
|
|||||||
protected stringCompare(s1: string, s2: string): boolean {
|
protected stringCompare(s1: string, s2: string): boolean {
|
||||||
return s1 === s2;
|
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 {
|
interface IDataTableOperation {
|
||||||
|
|||||||
@@ -94,8 +94,4 @@ export default class TableCommands {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetColumns(viewModel: TableEntityListViewModel): void {
|
|
||||||
viewModel.reloadTable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
@@ -101,7 +101,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
public useSetting: boolean = true;
|
public useSetting: boolean = true;
|
||||||
|
|
||||||
//public tableExplorerContext: TableExplorerContext;
|
//public tableExplorerContext: TableExplorerContext;
|
||||||
public notifyColumnChanges: (enablePrompt: boolean, queryTablesTab: QueryTablesTab) => void;
|
public notifyColumnChanges: (enablePrompt: boolean, queryTablesTab: NewQueryTablesTab) => void;
|
||||||
|
|
||||||
public tablePageStartIndex: number;
|
public tablePageStartIndex: number;
|
||||||
public tableQuery: Entities.ITableQuery = {};
|
public tableQuery: Entities.ITableQuery = {};
|
||||||
public cqlQuery: ko.Observable<string>;
|
public cqlQuery: ko.Observable<string>;
|
||||||
@@ -112,7 +113,7 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
public queryErrorMessage: ko.Observable<string>;
|
public queryErrorMessage: ko.Observable<string>;
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
constructor(tableCommands: TableCommands, queryTablesTab: QueryTablesTab) {
|
constructor(tableCommands: TableCommands, queryTablesTab: NewQueryTablesTab) {
|
||||||
super();
|
super();
|
||||||
this.cache = new TableEntityCache();
|
this.cache = new TableEntityCache();
|
||||||
this.queryErrorMessage = ko.observable<string>();
|
this.queryErrorMessage = ko.observable<string>();
|
||||||
@@ -131,24 +132,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
return [{ key: Constants.EntityKeyNames.RowKey, value: rowKey }];
|
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 {
|
public updateHeaders(newHeaders: string[], notifyColumnChanges: boolean = false, enablePrompt: boolean = true): void {
|
||||||
this.headers = newHeaders;
|
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.
|
* fnCallback - is the render callback with data to render.
|
||||||
* oSetting: current settings used for table initialization.
|
* 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 tablePageSize: number;
|
||||||
var draw: number;
|
|
||||||
var prefetchNeeded = true;
|
var prefetchNeeded = true;
|
||||||
var columnSortOrder: any;
|
|
||||||
// Threshold(pages) for triggering cache prefetch.
|
// Threshold(pages) for triggering cache prefetch.
|
||||||
// If number remaining pages in cache falls below prefetchThreshold prefetch will be triggered.
|
// If number remaining pages in cache falls below prefetchThreshold prefetch will be triggered.
|
||||||
var prefetchThreshold = 10;
|
var prefetchThreshold = 10;
|
||||||
var tableQuery = this.tableQuery;
|
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.
|
// Try cache if valid.
|
||||||
if (this.isCacheValid(tableQuery)) {
|
if (this.isCacheValid(tableQuery)) {
|
||||||
// Check if prefetch needed.
|
// Check if prefetch needed.
|
||||||
if (this.tablePageStartIndex + tablePageSize <= this.cache.length || this.allDownloaded) {
|
if (this.tablePageStartIndex + tablePageSize <= this.cache.length || this.allDownloaded) {
|
||||||
prefetchNeeded = false;
|
prefetchNeeded = false;
|
||||||
if (columnSortOrder && (!this.cache.sortOrder || !_.isEqual(this.cache.sortOrder, columnSortOrder))) {
|
this.tablePageStartIndex = 0;
|
||||||
this.sortColumns(columnSortOrder, oSettings);
|
this.renderPage(this.tablePageStartIndex, this.cache.length);
|
||||||
}
|
|
||||||
this.renderPage(fnCallback, draw, this.tablePageStartIndex, tablePageSize, oSettings);
|
|
||||||
if (
|
if (
|
||||||
!this.allDownloaded &&
|
!this.allDownloaded &&
|
||||||
this.tablePageStartIndex > 0 && // This is a case now that we can hit this as we re-construct table when we update column
|
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) {
|
if (prefetchNeeded) {
|
||||||
var downloadSize = tableQuery.top || this.downloadSize;
|
var downloadSize = tableQuery.top || this.downloadSize;
|
||||||
this.prefetchAndRender(
|
return await this.prefetchAndRender(tableQuery, 0, tablePageSize, downloadSize);
|
||||||
tableQuery,
|
} else {
|
||||||
this.tablePageStartIndex,
|
return this.cache.data;
|
||||||
tablePageSize,
|
|
||||||
downloadSize,
|
|
||||||
draw,
|
|
||||||
fnCallback,
|
|
||||||
oSettings,
|
|
||||||
columnSortOrder
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addEntityToCache(entity: Entities.ITableEntity): Q.Promise<any> {
|
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.
|
// Delay the add operation if we are fetching data from server, so as to avoid race condition.
|
||||||
if (this.cache.serverCallInProgress) {
|
if (this.cache.serverCallInProgress) {
|
||||||
return Utilities.delay(this.pollingInterval).then(() => {
|
Utilities.delay(this.pollingInterval).then(() => {
|
||||||
return this.updateCachedEntity(entity);
|
this.updateCachedEntity(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first item which is greater than the added entity.
|
this.cache.data.splice(this.cache.length, 0, 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);
|
|
||||||
|
|
||||||
// Finally, select newly added entity
|
// Finally, select newly added entity
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
@@ -254,8 +200,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
public updateCachedEntity(entity: Entities.ITableEntity): Q.Promise<any> {
|
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.
|
// Delay the add operation if we are fetching data from server, so as to avoid race condition.
|
||||||
if (this.cache.serverCallInProgress) {
|
if (this.cache.serverCallInProgress) {
|
||||||
return Utilities.delay(this.pollingInterval).then(() => {
|
Utilities.delay(this.pollingInterval).then(() => {
|
||||||
return this.updateCachedEntity(entity);
|
this.updateCachedEntity(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var oldEntityIndex: number = _.findIndex(
|
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.
|
// Delay the remove operation if we are fetching data from server, so as to avoid race condition.
|
||||||
if (this.cache.serverCallInProgress) {
|
if (this.cache.serverCallInProgress) {
|
||||||
return Utilities.delay(this.pollingInterval).then(() => {
|
Utilities.delay(this.pollingInterval).then(() => {
|
||||||
return this.removeEntitiesFromCache(entities);
|
this.removeEntitiesFromCache(entities);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,14 +238,6 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
});
|
});
|
||||||
this.clearSelection();
|
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);
|
return Q.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,86 +330,75 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private prefetchAndRender(
|
private async prefetchAndRender(
|
||||||
tableQuery: Entities.ITableQuery,
|
tableQuery: Entities.ITableQuery,
|
||||||
tablePageStartIndex: number,
|
tablePageStartIndex: number,
|
||||||
tablePageSize: number,
|
tablePageSize: number,
|
||||||
downloadSize: number,
|
downloadSize: number
|
||||||
draw: number,
|
): Promise<Entities.ITableEntity[]> {
|
||||||
renderCallBack: Function,
|
|
||||||
oSettings: any,
|
|
||||||
columnSortOrder: any
|
|
||||||
): void {
|
|
||||||
this.queryErrorMessage(null);
|
this.queryErrorMessage(null);
|
||||||
if (this.cache.serverCallInProgress) {
|
if (this.cache.serverCallInProgress) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
this.prefetchData(tableQuery, downloadSize, /* currentRetry */ 0)
|
try {
|
||||||
.then((result: IListTableEntitiesSegmentedResult) => {
|
const result = await this.prefetchData(tableQuery, downloadSize, /* currentRetry */ 0);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
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;
|
return result;
|
||||||
if (userContext.apiType === "Cassandra" && DataTableUtilities.checkForDefaultHeader(this.headers)) {
|
} catch (error) {
|
||||||
(<CassandraAPIDataClient>this.queryTablesTab.container.tableDataClient)
|
const parsedErrors = parseError(error);
|
||||||
.getTableSchema(this.queryTablesTab.collection)
|
var errors = parsedErrors.map((error) => {
|
||||||
.then((headers: CassandraTableKey[]) => {
|
return <ViewModels.QueryError>{
|
||||||
this.updateHeaders(
|
message: error.message,
|
||||||
headers.map((header) => header.property),
|
start: error.location ? error.location.start : undefined,
|
||||||
true
|
end: error.location ? error.location.end : undefined,
|
||||||
);
|
code: error.code,
|
||||||
});
|
severity: error.severity,
|
||||||
} 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();
|
|
||||||
});
|
});
|
||||||
|
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.
|
* 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
|
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
|
||||||
*/
|
*/
|
||||||
private prefetchData(
|
|
||||||
|
private async prefetchData(
|
||||||
tableQuery: Entities.ITableQuery,
|
tableQuery: Entities.ITableQuery,
|
||||||
downloadSize: number,
|
downloadSize: number,
|
||||||
currentRetry: number = 0
|
currentRetry: number = 0
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
|
var entities: any;
|
||||||
if (!this.cache.serverCallInProgress) {
|
if (!this.cache.serverCallInProgress) {
|
||||||
this.cache.serverCallInProgress = true;
|
this.cache.serverCallInProgress = true;
|
||||||
this.allDownloaded = false;
|
this.allDownloaded = false;
|
||||||
this.lastPrefetchTime = new Date().getTime();
|
this.lastPrefetchTime = new Date().getTime();
|
||||||
var time = this.lastPrefetchTime;
|
var time = this.lastPrefetchTime;
|
||||||
|
|
||||||
var promise: Q.Promise<IListTableEntitiesSegmentedResult>;
|
try {
|
||||||
if (this._documentIterator && this.continuationToken) {
|
if (this._documentIterator && this.continuationToken) {
|
||||||
// TODO handle Cassandra case
|
// TODO handle Cassandra case
|
||||||
|
const fetchNext = await this._documentIterator.fetchNext();
|
||||||
promise = Q(this._documentIterator.fetchNext().then((response) => response.resources)).then(
|
let fetchNextEntities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(
|
||||||
(documents: any[]) => {
|
fetchNext.resources
|
||||||
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
|
);
|
||||||
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
|
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
|
||||||
Results: entities,
|
Results: fetchNextEntities,
|
||||||
ContinuationToken: this._documentIterator.hasMoreResults(),
|
ContinuationToken: this._documentIterator.hasMoreResults(),
|
||||||
};
|
};
|
||||||
return Q.resolve(finalEntities);
|
entities = finalEntities;
|
||||||
}
|
} else if (this.continuationToken && userContext.apiType === "Cassandra") {
|
||||||
);
|
entities = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||||
} else if (this.continuationToken && userContext.apiType === "Cassandra") {
|
|
||||||
promise = Q(
|
|
||||||
this.queryTablesTab.container.tableDataClient.queryDocuments(
|
|
||||||
this.queryTablesTab.collection,
|
this.queryTablesTab.collection,
|
||||||
this.cqlQuery(),
|
this.cqlQuery(),
|
||||||
true,
|
true,
|
||||||
this.continuationToken
|
this.continuationToken
|
||||||
)
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
let query = this.sqlQuery();
|
||||||
let query = this.sqlQuery();
|
if (userContext.apiType === "Cassandra") {
|
||||||
if (userContext.apiType === "Cassandra") {
|
query = this.cqlQuery();
|
||||||
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)
|
const result = entities;
|
||||||
);
|
if (result) {
|
||||||
}
|
|
||||||
return promise
|
|
||||||
.then((result: IListTableEntitiesSegmentedResult) => {
|
|
||||||
if (!this._documentIterator) {
|
if (!this._documentIterator) {
|
||||||
this._documentIterator = result.iterator;
|
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.
|
// And as another service call is during process, we don't set serverCallInProgress to false here.
|
||||||
// Thus, end the prefetch.
|
// Thus, end the prefetch.
|
||||||
if (this.lastPrefetchTime !== time) {
|
if (this.lastPrefetchTime !== time) {
|
||||||
return Q.resolve(null);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = result.Results;
|
var entities = result.Results;
|
||||||
actualDownloadSize = entities.length;
|
actualDownloadSize = entities.length;
|
||||||
|
|
||||||
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
|
// 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) {
|
if (!this.continuationToken) {
|
||||||
this.allDownloaded = true;
|
this.allDownloaded = true;
|
||||||
@@ -568,30 +496,20 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
this.allDownloaded = true;
|
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) {
|
if (this.allDownloaded || nextDownloadSize === 0) {
|
||||||
return Q.resolve(result);
|
return Promise.resolve(this.cache.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
||||||
result.ExceedMaximumRetries = true;
|
result.ExceedMaximumRetries = true;
|
||||||
return Q.resolve(result);
|
return Promise.resolve(this.cache.data);
|
||||||
}
|
}
|
||||||
return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
||||||
})
|
}
|
||||||
.catch((error: Error) => {
|
} catch (error) {
|
||||||
this.cache.serverCallInProgress = false;
|
this.cache.serverCallInProgress = false;
|
||||||
return Q.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { IQueryTableRowsType } from "../../Tabs/QueryTablesTab/QueryTableTabUtils";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||||
@@ -106,14 +107,14 @@ export default class QueryBuilderViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setExample() {
|
public setExample(pk: string, rk: string) {
|
||||||
var example1 = new QueryClauseViewModel(
|
var example1 = new QueryClauseViewModel(
|
||||||
this,
|
this,
|
||||||
"",
|
"",
|
||||||
"PartitionKey",
|
"PartitionKey",
|
||||||
this.edmTypes()[0],
|
this.edmTypes()[0],
|
||||||
Constants.Operator.Equal,
|
Constants.Operator.Equal,
|
||||||
this.tableEntityListViewModel.items()[0].PartitionKey._,
|
pk,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@@ -127,7 +128,7 @@ export default class QueryBuilderViewModel {
|
|||||||
"RowKey",
|
"RowKey",
|
||||||
this.edmTypes()[0],
|
this.edmTypes()[0],
|
||||||
Constants.Operator.Equal,
|
Constants.Operator.Equal,
|
||||||
this.tableEntityListViewModel.items()[0].RowKey._,
|
rk,
|
||||||
true,
|
true,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@@ -139,40 +140,31 @@ export default class QueryBuilderViewModel {
|
|||||||
this.addClauseImpl(example2, 1);
|
this.addClauseImpl(example2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getODataFilterFromClauses = (): string => {
|
public getODataFilterFromClauses = (queryClauses: IQueryTableRowsType[]): string => {
|
||||||
var filterString: string = "";
|
var filterString: string = "";
|
||||||
var treeTraversal = (group: ClauseGroup): void => {
|
if (queryClauses != undefined) {
|
||||||
for (var i = 0; i < group.children.length; i++) {
|
for (var i = 0; i < queryClauses.length; i++) {
|
||||||
var currentItem = group.children[i];
|
var currentItem = queryClauses[i];
|
||||||
|
|
||||||
if (currentItem instanceof QueryClauseViewModel) {
|
this.timestampToValue(currentItem);
|
||||||
var clause = <QueryClauseViewModel>currentItem;
|
filterString = filterString.concat(
|
||||||
this.timestampToValue(clause);
|
this.constructODataClause(
|
||||||
filterString = filterString.concat(
|
filterString === "" ? "" : currentItem.selectedOperation,
|
||||||
this.constructODataClause(
|
this.generateLeftParentheses(currentItem),
|
||||||
filterString === "" ? "" : clause.and_or(),
|
currentItem.selectedField,
|
||||||
this.generateLeftParentheses(clause),
|
currentItem.selectedEntityType,
|
||||||
clause.field(),
|
currentItem.selectedOperator,
|
||||||
clause.type(),
|
currentItem.entityValue,
|
||||||
clause.operator(),
|
this.generateRightParentheses(currentItem)
|
||||||
clause.value(),
|
)
|
||||||
this.generateRightParentheses(clause)
|
);
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentItem instanceof ClauseGroup) {
|
|
||||||
treeTraversal(<ClauseGroup>currentItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
treeTraversal(this.queryClauses);
|
|
||||||
|
|
||||||
return filterString.trim();
|
return filterString.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
public getSqlFilterFromClauses = (): string => {
|
public getSqlFilterFromClauses = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||||
var filterString: string = "SELECT * FROM c";
|
var filterString: string = "SELECT * FROM c";
|
||||||
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
||||||
filterString = "SELECT";
|
filterString = "SELECT";
|
||||||
@@ -195,48 +187,37 @@ export default class QueryBuilderViewModel {
|
|||||||
});
|
});
|
||||||
filterString = filterString.concat(" FROM c");
|
filterString = filterString.concat(" FROM c");
|
||||||
}
|
}
|
||||||
if (this.queryClauses.children.length === 0) {
|
if (queryTableRows.length === 0) {
|
||||||
return filterString;
|
return filterString;
|
||||||
}
|
}
|
||||||
filterString = filterString.concat(" WHERE");
|
filterString = filterString.concat(" WHERE");
|
||||||
var first = true;
|
var first = true;
|
||||||
var treeTraversal = (group: ClauseGroup): void => {
|
for (var i = 0; i < queryTableRows.length; i++) {
|
||||||
for (var i = 0; i < group.children.length; i++) {
|
var currentItem = queryTableRows[i];
|
||||||
var currentItem = group.children[i];
|
|
||||||
|
|
||||||
if (currentItem instanceof QueryClauseViewModel) {
|
let timeStampValue: string = this.timestampToSqlValue(currentItem);
|
||||||
var clause = <QueryClauseViewModel>currentItem;
|
var value = currentItem.entityValue;
|
||||||
let timeStampValue: string = this.timestampToSqlValue(clause);
|
if (!currentItem.isValue) {
|
||||||
var value = clause.value();
|
value = timeStampValue;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
filterString = filterString.concat(
|
||||||
|
this.constructSqlClause(
|
||||||
treeTraversal(this.queryClauses);
|
first ? "" : currentItem.selectedOperation,
|
||||||
|
this.generateLeftParentheses(currentItem),
|
||||||
|
currentItem.selectedField,
|
||||||
|
currentItem.selectedEntityType,
|
||||||
|
currentItem.selectedOperator,
|
||||||
|
value,
|
||||||
|
this.generateRightParentheses(currentItem)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
return filterString.trim();
|
return filterString.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
public getCqlFilterFromClauses = (): string => {
|
public getCqlFilterFromClauses = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||||
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
|
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
|
||||||
const collectionId = this._queryViewModel.queryTablesTab.collection.id();
|
const collectionId = this._queryViewModel.queryTablesTab.collection.id();
|
||||||
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
|
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
|
||||||
@@ -251,43 +232,32 @@ export default class QueryBuilderViewModel {
|
|||||||
});
|
});
|
||||||
filterString = filterString.concat(` FROM ${tableToQuery}`);
|
filterString = filterString.concat(` FROM ${tableToQuery}`);
|
||||||
}
|
}
|
||||||
if (this.queryClauses.children.length === 0) {
|
if (queryTableRows === undefined || queryTableRows.length === 0) {
|
||||||
return filterString;
|
return filterString;
|
||||||
}
|
}
|
||||||
filterString = filterString.concat(" WHERE");
|
filterString = filterString.concat(" WHERE");
|
||||||
var first = true;
|
var first = true;
|
||||||
var treeTraversal = (group: ClauseGroup): void => {
|
for (var i = 0; i < queryTableRows.length; i++) {
|
||||||
for (var i = 0; i < group.children.length; i++) {
|
var currentItem = queryTableRows[i];
|
||||||
var currentItem = group.children[i];
|
|
||||||
|
|
||||||
if (currentItem instanceof QueryClauseViewModel) {
|
let timeStampValue: string = this.timestampToSqlValue(currentItem);
|
||||||
var clause = <QueryClauseViewModel>currentItem;
|
var value = currentItem.entityValue;
|
||||||
let timeStampValue: string = this.timestampToSqlValue(clause);
|
if (!currentItem.isValue) {
|
||||||
var value = clause.value();
|
value = timeStampValue;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
filterString = filterString.concat(
|
||||||
|
this.constructCqlClause(
|
||||||
treeTraversal(this.queryClauses);
|
first ? "" : currentItem.selectedOperation,
|
||||||
|
this.generateLeftParentheses(currentItem),
|
||||||
|
currentItem.selectedField,
|
||||||
|
currentItem.selectedEntityType,
|
||||||
|
currentItem.selectedOperator,
|
||||||
|
value,
|
||||||
|
this.generateRightParentheses(currentItem)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
return filterString.trim();
|
return filterString.trim();
|
||||||
};
|
};
|
||||||
@@ -298,7 +268,7 @@ export default class QueryBuilderViewModel {
|
|||||||
this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns));
|
this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns));
|
||||||
};
|
};
|
||||||
|
|
||||||
private generateLeftParentheses(clause: QueryClauseViewModel): string {
|
private generateLeftParentheses(clause: IQueryTableRowsType): string {
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
||||||
if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) {
|
if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) {
|
||||||
@@ -321,7 +291,7 @@ export default class QueryBuilderViewModel {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateRightParentheses(clause: QueryClauseViewModel): string {
|
private generateRightParentheses(clause: IQueryTableRowsType): string {
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -504,7 +474,7 @@ export default class QueryBuilderViewModel {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onAddNewClauseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onAddNewClauseKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): boolean => {
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||||
this.addClauseIndex(this.clauseArray().length - 1, null);
|
this.addClauseIndex(this.clauseArray().length - 1, null);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -630,16 +600,11 @@ export default class QueryBuilderViewModel {
|
|||||||
return groupViewModels;
|
return groupViewModels;
|
||||||
};
|
};
|
||||||
|
|
||||||
public runQuery = (): DataTables.DataTable => {
|
|
||||||
return this._queryViewModel.runQuery();
|
|
||||||
};
|
|
||||||
|
|
||||||
public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void {
|
public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void {
|
||||||
var index = this.clauseArray.peek().indexOf(clauseToAdd);
|
var index = this.clauseArray.peek().indexOf(clauseToAdd);
|
||||||
|
|
||||||
var newClause = new QueryClauseViewModel(
|
var newClause = new QueryClauseViewModel(
|
||||||
this,
|
this,
|
||||||
//this._tableEntityListViewModel.tableExplorerContext.hostProxy,
|
|
||||||
"And",
|
"And",
|
||||||
clauseToAdd.field(),
|
clauseToAdd.field(),
|
||||||
"DateTime",
|
"DateTime",
|
||||||
@@ -713,67 +678,67 @@ export default class QueryBuilderViewModel {
|
|||||||
//DataTableUtilities.forceRecalculateTableSize();
|
//DataTableUtilities.forceRecalculateTableSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private timestampToValue(clause: QueryClauseViewModel): void {
|
private timestampToValue(clause: IQueryTableRowsType): void {
|
||||||
if (clause.isValue()) {
|
if (clause.isValue) {
|
||||||
return;
|
return;
|
||||||
} else if (clause.isTimestamp()) {
|
} else if (clause.isTimestamp) {
|
||||||
this.getTimeStampToQuery(clause);
|
this.getTimeStampToQuery(clause);
|
||||||
// } else if (clause.isCustomLastTimestamp()) {
|
// } else if (clause.isCustomLastTimestamp()) {
|
||||||
// clause.value(`datetime'${CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit)}'`);
|
// clause.value(`datetime'${CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit)}'`);
|
||||||
} else if (clause.isCustomRangeTimestamp()) {
|
} else if (clause.isCustomRangeTimestamp) {
|
||||||
if (clause.isLocal()) {
|
if (clause.isLocal) {
|
||||||
clause.value(`datetime'${DateTimeUtilities.getUTCDateTime(clause.customTimeValue())}'`);
|
clause.entityValue = `datetime'${DateTimeUtilities.getUTCDateTime(clause.customTimeValue)}'`;
|
||||||
} else {
|
} else {
|
||||||
clause.value(`datetime'${clause.customTimeValue()}Z'`);
|
clause.entityValue = `datetime'${clause.customTimeValue}Z'`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private timestampToSqlValue(clause: QueryClauseViewModel): string {
|
private timestampToSqlValue(clause: IQueryTableRowsType): string {
|
||||||
if (clause.isValue()) {
|
if (clause.isValue) {
|
||||||
return null;
|
return null;
|
||||||
} else if (clause.isTimestamp()) {
|
} else if (clause.isTimestamp) {
|
||||||
return this.getTimeStampToSqlQuery(clause);
|
return this.getTimeStampToSqlQuery(clause);
|
||||||
// } else if (clause.isCustomLastTimestamp()) {
|
// } else if (clause.isCustomLastTimestamp()) {
|
||||||
// clause.value(CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit));
|
// clause.value(CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit));
|
||||||
} else if (clause.isCustomRangeTimestamp()) {
|
} else if (clause.isCustomRangeTimestamp) {
|
||||||
if (clause.isLocal()) {
|
if (clause.isLocal) {
|
||||||
return DateTimeUtilities.getUTCDateTime(clause.customTimeValue());
|
return DateTimeUtilities.getUTCDateTime(clause.customTimeValue);
|
||||||
} else {
|
} else {
|
||||||
return clause.customTimeValue();
|
return clause.customTimeValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTimeStampToQuery(clause: QueryClauseViewModel): void {
|
private getTimeStampToQuery(clause: IQueryTableRowsType): void {
|
||||||
switch (clause.timeValue()) {
|
switch (clause.timeValue) {
|
||||||
case Constants.timeOptions.lastHour:
|
case Constants.timeOptions.lastHour:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(0, 1)}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(0, 1)}'`;
|
||||||
break;
|
break;
|
||||||
case Constants.timeOptions.last24Hours:
|
case Constants.timeOptions.last24Hours:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(0, 24)}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(0, 24)}'`;
|
||||||
break;
|
break;
|
||||||
case Constants.timeOptions.last7Days:
|
case Constants.timeOptions.last7Days:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(7, 0)}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(7, 0)}'`;
|
||||||
break;
|
break;
|
||||||
case Constants.timeOptions.last31Days:
|
case Constants.timeOptions.last31Days:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(31, 0)}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(31, 0)}'`;
|
||||||
break;
|
break;
|
||||||
case Constants.timeOptions.last365Days:
|
case Constants.timeOptions.last365Days:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(365, 0)}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryLastDaysHours(365, 0)}'`;
|
||||||
break;
|
break;
|
||||||
case Constants.timeOptions.currentMonth:
|
case Constants.timeOptions.currentMonth:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryCurrentMonthLocal()}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryCurrentMonthLocal()}'`;
|
||||||
break;
|
break;
|
||||||
case Constants.timeOptions.currentYear:
|
case Constants.timeOptions.currentYear:
|
||||||
clause.value(`datetime'${CustomTimestampHelper._queryCurrentYearLocal()}'`);
|
clause.entityValue = `datetime'${CustomTimestampHelper._queryCurrentYearLocal()}'`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTimeStampToSqlQuery(clause: QueryClauseViewModel): string {
|
private getTimeStampToSqlQuery(clause: IQueryTableRowsType): string {
|
||||||
switch (clause.timeValue()) {
|
switch (clause.timeValue) {
|
||||||
case Constants.timeOptions.lastHour:
|
case Constants.timeOptions.lastHour:
|
||||||
return CustomTimestampHelper._queryLastDaysHours(0, 1);
|
return CustomTimestampHelper._queryLastDaysHours(0, 1);
|
||||||
case Constants.timeOptions.last24Hours:
|
case Constants.timeOptions.last24Hours:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { KeyCodes } from "../../../Common/Constants";
|
|||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
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 { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||||
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
||||||
@@ -39,14 +40,14 @@ export default class QueryViewModel {
|
|||||||
|
|
||||||
public columnOptions: ko.ObservableArray<string>;
|
public columnOptions: ko.ObservableArray<string>;
|
||||||
|
|
||||||
public queryTablesTab: QueryTablesTab;
|
public queryTablesTab: NewQueryTablesTab;
|
||||||
public id: string;
|
public id: string;
|
||||||
private _tableEntityListViewModel: TableEntityListViewModel;
|
private _tableEntityListViewModel: TableEntityListViewModel;
|
||||||
|
|
||||||
constructor(queryTablesTab: QueryTablesTab) {
|
constructor(queryTablesTab: NewQueryTablesTab) {
|
||||||
this.queryTablesTab = queryTablesTab;
|
this.queryTablesTab = queryTablesTab;
|
||||||
this.id = `queryViewModel${this.queryTablesTab.tabId}`;
|
this.id = `queryViewModel${this.queryTablesTab.tabId}`;
|
||||||
this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel();
|
this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel;
|
||||||
|
|
||||||
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
||||||
return userContext.apiType !== "Cassandra";
|
return userContext.apiType !== "Cassandra";
|
||||||
@@ -103,7 +104,7 @@ export default class QueryViewModel {
|
|||||||
DataTableUtilities.forceRecalculateTableSize();
|
DataTableUtilities.forceRecalculateTableSize();
|
||||||
};
|
};
|
||||||
|
|
||||||
public toggleAdvancedOptions = () => {
|
public toggleAdvancedOptions = (): void => {
|
||||||
this.isExpanded(!this.isExpanded());
|
this.isExpanded(!this.isExpanded());
|
||||||
if (this.isExpanded()) {
|
if (this.isExpanded()) {
|
||||||
this.focusTopResult(true);
|
this.focusTopResult(true);
|
||||||
@@ -126,23 +127,19 @@ export default class QueryViewModel {
|
|||||||
return this.selectText();
|
return this.selectText();
|
||||||
};
|
};
|
||||||
|
|
||||||
private setFilter = (): string => {
|
private setFilter = (queryTableRows?: IQueryTableRowsType[]): string => {
|
||||||
const queryString = this.isEditorActive()
|
const queryString = this.isEditorActive()
|
||||||
? this.queryText()
|
? this.queryText()
|
||||||
: userContext.apiType === "Cassandra"
|
: userContext.apiType === "Cassandra"
|
||||||
? this.queryBuilderViewModel().getCqlFilterFromClauses()
|
? this.queryBuilderViewModel().getCqlFilterFromClauses(queryTableRows)
|
||||||
: this.queryBuilderViewModel().getODataFilterFromClauses();
|
: this.queryBuilderViewModel().getODataFilterFromClauses(queryTableRows);
|
||||||
const filter = queryString;
|
const filter = queryString;
|
||||||
this.queryText(filter);
|
this.queryText(filter);
|
||||||
return this.queryText();
|
return this.queryText();
|
||||||
};
|
};
|
||||||
|
|
||||||
private setSqlFilter = (): string => {
|
private setSqlFilter = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||||
return this.queryBuilderViewModel().getSqlFilterFromClauses();
|
return this.queryBuilderViewModel().getSqlFilterFromClauses(queryTableRows);
|
||||||
};
|
|
||||||
|
|
||||||
private setCqlFilter = (): string => {
|
|
||||||
return this.queryBuilderViewModel().getCqlFilterFromClauses();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public isHelperEnabled = ko
|
public isHelperEnabled = ko
|
||||||
@@ -158,8 +155,9 @@ export default class QueryViewModel {
|
|||||||
notify: "always",
|
notify: "always",
|
||||||
});
|
});
|
||||||
|
|
||||||
public runQuery = (): DataTables.DataTable => {
|
public runQuery = (queryTableRows: IQueryTableRowsType[]): string => {
|
||||||
let filter = this.setFilter();
|
let filter = this.setFilter(queryTableRows);
|
||||||
|
|
||||||
if (filter && userContext.apiType !== "Cassandra") {
|
if (filter && userContext.apiType !== "Cassandra") {
|
||||||
filter = filter.replace(/"/g, "'");
|
filter = filter.replace(/"/g, "'");
|
||||||
}
|
}
|
||||||
@@ -170,13 +168,15 @@ export default class QueryViewModel {
|
|||||||
this._tableEntityListViewModel.tableQuery.top = top;
|
this._tableEntityListViewModel.tableQuery.top = top;
|
||||||
this._tableEntityListViewModel.tableQuery.select = select;
|
this._tableEntityListViewModel.tableQuery.select = select;
|
||||||
this._tableEntityListViewModel.oDataQuery(filter);
|
this._tableEntityListViewModel.oDataQuery(filter);
|
||||||
this._tableEntityListViewModel.sqlQuery(this.setSqlFilter());
|
this._tableEntityListViewModel.sqlQuery(this.setSqlFilter(queryTableRows));
|
||||||
this._tableEntityListViewModel.cqlQuery(filter);
|
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.queryText();
|
||||||
this.topValue();
|
this.topValue();
|
||||||
this.selectText();
|
this.selectText();
|
||||||
@@ -194,16 +194,26 @@ export default class QueryViewModel {
|
|||||||
this.queryTablesTab.collection.id()
|
this.queryTablesTab.collection.id()
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
return this._tableEntityListViewModel.reloadTable(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public selectQueryOptions() {
|
public selectQueryOptions(headers: string[], getSelectMessage: (selectMessage: string) => void): void {
|
||||||
useSidePanel.getState().openSidePanel("Select Column", <TableQuerySelectPanel queryViewModel={this} />);
|
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) {
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||||
this.selectQueryOptions();
|
this.selectQueryOptions(headers, getSelectMessage);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DetailsList, DetailsListLayoutMode, IColumn, Pivot, PivotItem, SelectionMode } from "@fluentui/react";
|
import { DetailsList, DetailsListLayoutMode, IColumn, Pivot, PivotItem, SelectionMode, Text } from "@fluentui/react";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import SplitterLayout from "react-splitter-layout";
|
import SplitterLayout from "react-splitter-layout";
|
||||||
import "react-splitter-layout/lib/index.css";
|
import "react-splitter-layout/lib/index.css";
|
||||||
@@ -120,21 +120,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
constructor(props: IQueryTabComponentProps) {
|
constructor(props: IQueryTabComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const columns: IColumn[] = [
|
const columns: IColumn[] = [
|
||||||
{
|
|
||||||
key: "column1",
|
|
||||||
name: "",
|
|
||||||
minWidth: 16,
|
|
||||||
maxWidth: 16,
|
|
||||||
data: String,
|
|
||||||
fieldName: "toolTip",
|
|
||||||
onRender: this.onRenderColumnItem,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "column2",
|
key: "column2",
|
||||||
name: "METRIC",
|
name: "METRIC",
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
data: String,
|
data: String,
|
||||||
fieldName: "metric",
|
fieldName: "metric",
|
||||||
|
onRender: this.onRenderColumnItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "column3",
|
key: "column3",
|
||||||
@@ -206,7 +198,12 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
|
|
||||||
public onRenderColumnItem(item: IDocument): JSX.Element {
|
public onRenderColumnItem(item: IDocument): JSX.Element {
|
||||||
if (item.toolTip !== "") {
|
if (item.toolTip !== "") {
|
||||||
return <InfoTooltip>{`${item.toolTip}`}</InfoTooltip>;
|
return (
|
||||||
|
<>
|
||||||
|
<InfoTooltip>{`${item.toolTip}`}</InfoTooltip>
|
||||||
|
<Text style={{ paddingLeft: 10, margin: 0 }}>{`${item.metric}`}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -1002,7 +999,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
"data-order": 2,
|
"data-order": 2,
|
||||||
"data-title": "Query Stats",
|
"data-title": "Query Stats",
|
||||||
}}
|
}}
|
||||||
style={{ height: "100%" }}
|
style={{ height: "100%", overflowY: "scroll" }}
|
||||||
>
|
>
|
||||||
{this.state.allResultsMetadata.length > 0 && !this.state.error && (
|
{this.state.allResultsMetadata.length > 0 && !this.state.error && (
|
||||||
<div className="queryMetricsSummaryContainer">
|
<div className="queryMetricsSummaryContainer">
|
||||||
|
|||||||
@@ -1,271 +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="link"
|
|
||||||
>
|
|
||||||
<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
@@ -1,5 +1,5 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||||
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
||||||
import errorIcon from "../../../images/close-black.svg";
|
import errorIcon from "../../../images/close-black.svg";
|
||||||
import { useObservable } from "../../hooks/useObservable";
|
import { useObservable } from "../../hooks/useObservable";
|
||||||
@@ -32,7 +32,13 @@ export const Tabs = (): JSX.Element => {
|
|||||||
|
|
||||||
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
|
const focusTab = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (active && focusTab.current) {
|
||||||
|
focusTab.current.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
onMouseOver={() => setHovering(true)}
|
onMouseOver={() => setHovering(true)}
|
||||||
@@ -46,6 +52,7 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
|||||||
aria-controls={tab.tabId}
|
aria-controls={tab.tabId}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="tab"
|
role="tab"
|
||||||
|
ref={focusTab}
|
||||||
>
|
>
|
||||||
<span className="tabNavContentContainer">
|
<span className="tabNavContentContainer">
|
||||||
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>
|
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
|||||||
language={"javascript"}
|
language={"javascript"}
|
||||||
content={udfBody}
|
content={udfBody}
|
||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
ariaLabel={"Graph JSON"}
|
ariaLabel={"User defined function body"}
|
||||||
onContentChanged={this.handleUdfBodyChange}
|
onContentChanged={this.handleUdfBodyChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user