Compare commits

..

27 Commits

Author SHA1 Message Date
hardiknai-techm
cf3082d30d Merge branch 'update_to_webpack_5' of https://github.com/Azure/cosmos-explorer into update_to_webpack_5 2021-05-27 10:41:47 +05:30
hardiknai-techm
dbfa7e37ed skipLibCheck added 2021-05-27 10:41:24 +05:30
Srinath Narayanan
1bf00f65b8 fixed lint error for MongoIndexPolicyComponentTest 2021-05-27 10:33:16 +05:30
hardiknai-techm
a20620db0e master merge 2021-05-27 09:34:42 +05:30
hardiknai-techm
5724ed81f9 merge maste 2021-05-15 09:37:29 +05:30
hardiknai-techm
349a54e078 prod script tag change 2021-05-15 09:35:59 +05:30
hardiknai-techm
bf9fdb95e9 marge master 2021-05-14 14:48:12 +05:30
hardiknai-techm
b4461ddf5d babel version update 2021-05-13 17:30:08 +05:30
hardiknai-techm
349a4bb0f2 react alies create in webapck 2021-05-13 16:49:57 +05:30
hardiknai-techm
5f977b9419 pull master 2021-05-12 09:47:19 +05:30
hardiknai-techm
adce85efd4 use npm version 6 2021-05-11 05:52:28 +05:30
hardiknai-techm
292d2f3be4 use npm version 6 2021-05-11 05:51:12 +05:30
hardiknai-techm
5613c822db resolve master merge conflict 2021-05-08 09:17:27 +05:30
hardiknai-techm
3c320167d8 resolve compile time error 2021-05-08 09:15:19 +05:30
hardiknai-techm
2c58cd7be1 merge master and resolve code conflict 2021-05-06 16:12:30 +05:30
hardiknai-techm
cc4f20e482 update babel and loader package 2021-05-06 06:50:36 +05:30
hardiknai-techm
10aa097166 resolve merge conflict 2021-05-05 08:52:52 +05:30
hardiknai-techm
9e7252dfeb add svgr plugin for svg as ReactComponent 2021-05-05 08:45:34 +05:30
hardiknai-techm
d8dff644d5 update sanpshort and run formattor 2021-05-03 10:37:49 +05:30
hardiknai-techm
2855bf4c7d update test:e2e file name 2021-05-03 09:51:16 +05:30
hardiknai-techm
4ffea1bad9 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into update_to_webpack_5 2021-05-03 08:53:54 +05:30
hardiknai-techm
3cdc30864b merge master 2021-05-03 08:53:28 +05:30
hardiknai-techm
f4a322d17d update react-dom version 2021-04-30 19:39:21 +05:30
hardiknai-techm
8e033bdd73 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into update_to_webpack_5 2021-04-29 21:32:18 +05:30
hardiknai-techm
7d2315f282 add process package it is remove in webpack5 2021-04-28 19:09:41 +05:30
hardiknai-techm
5b467e239a resolve master merge 2021-04-28 18:17:33 +05:30
hardiknai-techm
d65600dd14 webpack and all loader as well plugin version update 2021-04-28 12:00:32 +05:30
595 changed files with 17491 additions and 13609 deletions

View File

@@ -44,6 +44,7 @@ src/Definitions/png.d.ts
src/Definitions/svg.d.ts
src/Explorer/ComponentRegisterer.test.ts
src/Explorer/ComponentRegisterer.ts
src/Explorer/ContextMenuButtonFactory.ts
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
src/Explorer/Controls/DynamicList/DynamicList.test.ts
@@ -104,10 +105,15 @@ src/Explorer/Notebook/NotebookContainerClient.ts
src/Explorer/Notebook/NotebookContentClient.ts
src/Explorer/Notebook/NotebookContentItem.ts
src/Explorer/Notebook/NotebookUtil.ts
src/Explorer/OpenActions.test.ts
src/Explorer/OpenActions.ts
src/Explorer/OpenActionsStubs.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
# src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts
@@ -132,6 +138,7 @@ src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryViewModel.ts
src/Explorer/Tables/TableDataClient.ts
src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts
@@ -141,11 +148,14 @@ src/Explorer/Tabs/DocumentsTab.test.ts
src/Explorer/Tabs/DocumentsTab.ts
src/Explorer/Tabs/GraphTab.ts
src/Explorer/Tabs/MongoDocumentsTab.ts
# src/Explorer/Tabs/MongoQueryTab.ts
# src/Explorer/Tabs/MongoShellTab.ts
src/Explorer/Tabs/MongoQueryTab.ts
src/Explorer/Tabs/MongoShellTab.ts
src/Explorer/Tabs/NotebookV2Tab.ts
src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts
# src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts
@@ -198,6 +208,9 @@ src/ResourceProvider/IResourceProviderClient.test.ts
src/ResourceProvider/IResourceProviderClient.ts
src/ResourceProvider/ResourceProviderClient.ts
src/ResourceProvider/ResourceProviderClientFactory.ts
src/RouteHandlers/RouteHandler.ts
src/RouteHandlers/TabRouteHandler.test.ts
src/RouteHandlers/TabRouteHandler.ts
src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts
@@ -253,6 +266,7 @@ src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorerAdapter.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx

View File

@@ -43,6 +43,7 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error",
"react/react-in-jsx-scope": "off",
"react/display-name": "off",
"react-hooks/rules-of-hooks": "warn", // TODO: error
"react-hooks/exhaustive-deps": "warn", // TODO: error

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch:
schedule:
# Once every hour
- cron: "0 15 * * *"
- cron: "0 * * * *"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:

View File

@@ -1,4 +1,13 @@
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"],
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
[
"@babel/preset-react",
{
runtime: "automatic",
},
],
"@babel/preset-typescript",
],
plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]],
};

View File

@@ -1,10 +0,0 @@
<svg width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M14.01 0.72999L4.81001 9.93999L0.0100098 5.17999L0.78001 4.40999L4.78001 8.40999L13.21 -0.0100098L14.01 0.72999Z" fill="#0078D4"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="14" height="9.99" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 371 B

View File

@@ -62,6 +62,8 @@ module.exports = {
// "node_modules"
// ],
modulePaths: ["node_modules", "<rootDir>/src"],
// An array of file extensions your modules use
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "html", "svg"],

View File

@@ -4,7 +4,7 @@
@font-face {
font-family: wf_segoe-ui_normal;
src: local("Segoe UI"), url("../../fonts/segoe-ui/west-european/normal/latest.woff");
src: url(./fonts/segoe-ui/west-european/normal/latest.woff);
}
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;

View File

@@ -4,166 +4,166 @@
*/
table.storage {
width: 100%;
margin: 6px 0px 0px 0px;
clear: both;
border-collapse: separate;
border-spacing: 0;
background-color: @BaseLight;
/*[{datatable-base-background-color}]*/
outline-width: 0;
/*Keyboard navigation - ensure table has no border when focused. */
/* no select */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 100%;
margin: 6px 0px 0px 0px;
clear: both;
border-collapse: separate;
border-spacing: 0;
background-color: @BaseLight;
/*[{datatable-base-background-color}]*/
outline-width: 0;
/*Keyboard navigation - ensure table has no border when focused. */
/* no select */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
table.dataTable.azure-table tbody tr {
color: #000000;
/*[{datatable-name-cell-text}]*/
color: #000000;
/*[{datatable-name-cell-text}]*/
}
table.dataTable.azure-table tbody tr td {
height: 16px;
height: 16px;
}
table.can-select {
/* no select */
-webkit-touch-callout: auto;
-webkit-user-select: auto;
-khtml-user-select: auto;
-moz-user-select: auto;
-ms-user-select: auto;
user-select: auto;
/* no select */
-webkit-touch-callout: auto;
-webkit-user-select: auto;
-khtml-user-select: auto;
-moz-user-select: auto;
-ms-user-select: auto;
user-select: auto;
}
table.can-select.dataTable tbody tr {
color: #000000;
/*[{datatable-name-cell-text}]*/
background-color: transparent;
/*[{datatable-base-background}]*/
color: #000000;
/*[{datatable-name-cell-text}]*/
background-color: transparent;
/*[{datatable-base-background}]*/
}
table.can-select.dataTable tbody tr.selected {
color: #000000 !important;
/*[{datatable-row-selected-text} !important]*/
background-color: #C9DEF5;
/*[{datatable-row-selected-background}]*/
border: 1px solid #b3d1f1;
/*[1px solid {datatable-row-selected-border}]*/
color: #000000 !important;
/*[{datatable-row-selected-text} !important]*/
background-color: #c9def5;
/*[{datatable-row-selected-background}]*/
border: 1px solid #b3d1f1;
/*[1px solid {datatable-row-selected-border}]*/
}
table.can-select.dataTable tbody tr.selected td:first-child {
color: #000000 !important;
/*[{datatable-name-cell-selected-text} !important]*/
color: #000000 !important;
/*[{datatable-name-cell-selected-text} !important]*/
}
table.can-select.dataTable.hover tbody tr:hover,
table.dataTable.storage tbody tr:hover {
background-color: #C9DEF5;
/*[{datatable-row-hover-background}]*/
background-color: #c9def5;
/*[{datatable-row-hover-background}]*/
}
table.dataTable.storage tbody tr:hover td:empty {
background-color: #C9DEF5;
/*[{datatable-row-hover-empty-background}]*/
background-color: #c9def5;
/*[{datatable-row-hover-empty-background}]*/
}
table.can-select.dataTable.hover tbody tr:hover.selected,
table.can-select.dataTable.storage tbody tr:hover.selected {
background-color: #C9DEF5;
/*[{datatable-row-selected-background}]*/
background-color: #c9def5;
/*[{datatable-row-selected-background}]*/
}
table.can-select.dataTable:not(:focus) tbody tr.selected {
color: #1E1E1E;
/*[{datatable-row-selected-text}]*/
background-color: #767676;
/*[{datatable-row-selected-background}]*/
color: #1e1e1e;
/*[{datatable-row-selected-text}]*/
background-color: #767676;
/*[{datatable-row-selected-background}]*/
}
table.can-select.dataTable.hover:not(:focus) tbody tr:hover.selected,
table.can-select.dataTable.storage:not(:focus) tbody tr:hover.selected {
background-color: #767676;
/*[{datatable-row-selected-background}]*/
background-color: #767676;
/*[{datatable-row-selected-background}]*/
}
table.storage thead th,
table.storage tfoot th {
font-weight: normal;
color: #DDDDDD;
/*[{datatable-header-text}]*/
white-space: nowrap;
font-weight: normal;
color: #dddddd;
/*[{datatable-header-text}]*/
white-space: nowrap;
}
table.storage thead th,
table.storage thead td {
padding: 0.5em 1em;
border: 1px solid #DDDDDD;
/*[1px solid {datatable-header-border}]*/
background-color: @BaseLight;
/*[{datatable-header-background}]*/
text-align: left;
color: #808080;
/*[{datatable-header-text}]*/
outline: none;
padding: 0.5em 1em;
border: 1px solid #dddddd;
/*[1px solid {datatable-header-border}]*/
background-color: @BaseLight;
/*[{datatable-header-background}]*/
text-align: left;
color: #808080;
/*[{datatable-header-text}]*/
outline: none;
}
table.dataTable thead th:active,
table.dataTable thead td:active {
border: 1px solid #DDDDDD;
/*[1px solid {datatable-header-cell-active-border}]*/
outline: none;
background-color: #B4C7DC !important;
/*[{datatable-header-cell-active-background} !important] */
border: 1px solid #dddddd;
/*[1px solid {datatable-header-cell-active-border}]*/
outline: none;
background-color: #b4c7dc !important;
/*[{datatable-header-cell-active-background} !important] */
}
table.dataTable thead th:focus,
table.dataTable thead td:focus {
border-width: 1px;
border-style: solid;
border-color: #007ACC;
/*[{datatable-header-cell-focus-background}]*/
border-width: 1px;
border-style: solid;
border-color: #007acc;
/*[{datatable-header-cell-focus-background}]*/
}
table.dataTable thead th:hover,
table.dataTable thead td:hover {
border: 1px solid #007ACC;
/*[1px solid {datatable-header-cell-hover-border}]*/
background-color: #C9DEF5;
/*[{datatable-header-cell-hover-background}]*/
color: #000000;
/*[{datatable-header-cell-hover-text}]*/
border: 1px solid #007acc;
/*[1px solid {datatable-header-cell-hover-border}]*/
background-color: #c9def5;
/*[{datatable-header-cell-hover-background}]*/
color: #000000;
/*[{datatable-header-cell-hover-text}]*/
}
table.storage thead th:not(:focus):not(:hover),
table.storage thead td:not(:focus):not(:hover) {
border-left-color: transparent;
border-left-color: transparent;
}
table.storage thead th:last-child:not(:focus):not(:hover),
table.storage thead td:last-child:not(:focus):not(:hover) {
border-right-color: transparent;
border-right-color: transparent;
}
table.dataTable tfoot th,
table.dataTable tfoot td {
padding: 5px 18px 5px 18px;
border-top: 1px solid #DDDDDD;
/*[1px solid {datatable-header-border}]*/
background-color: @BaseLight;
/*[{datatable-header-background}]*/
padding: 5px 18px 5px 18px;
border-top: 1px solid #dddddd;
/*[1px solid {datatable-header-border}]*/
background-color: @BaseLight;
/*[{datatable-header-background}]*/
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc {
cursor: pointer;
*cursor: hand;
cursor: pointer;
*cursor: hand;
}
table.dataTable thead .sorting,
@@ -171,367 +171,365 @@ table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
background-repeat: no-repeat;
background-position: center right;
background-repeat: no-repeat;
background-position: center right;
}
table.dataTable thead .sorting_asc {
background-image: url("../../images/QueryBuilder/CollapseChevronUp_16x.png");
background-image: url(/images/QueryBuilder/CollapseChevronUp_16x.png);
}
table.dataTable thead .sorting_desc {
background-image: url("../../images/QueryBuilder/CollapseChevronDown_16x.png");
background-image: url(/images/QueryBuilder/CollapseChevronDown_16x.png);
}
table.dataTable tbody tr {
color: #808080;
/*[{datatable-base-text}]*/
background-color: @BaseLight;
/*[{datatable-base-background}]*/
color: #808080;
/*[{datatable-base-text}]*/
background-color: @BaseLight;
/*[{datatable-base-background}]*/
}
table.dataTable tbody tr.selected {
color: @BaseLight !important;
/*[{datatable-row-selected-text} !important]*/
background-color: @SelectionColor;
/*[{datatable-row-selected-background]*/
color: @BaseLight !important;
/*[{datatable-row-selected-text} !important]*/
background-color: @SelectionColor;
/*[{datatable-row-selected-background]*/
}
table.dataTable tbody tr.selected td:first-child,
table.dataTable tbody tr.selected td:nth-child(2) {
color: @BaseLight !important;
/*[{datatable-row-selected-text} !important]*/
color: @BaseLight !important;
/*[{datatable-row-selected-text} !important]*/
}
table.dataTable tbody tr td:first-child img {
vertical-align: middle;
vertical-align: middle;
}
table.dataTable tbody tr td:first-child {
border-left-width: 1px;
border-left-width: 1px;
}
table.dataTable tbody th,
table.dataTable tbody td {
padding: 0.25em 0.5em;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60em;
white-space: nowrap;
padding: 0.25em 0.5em;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60em;
white-space: nowrap;
}
table.show-gridlines tbody th,
table.show-gridlines tbody td {
border-width: 1px 1px 1px 0px;
border-style: solid;
border-color: #DDDDDD;
/*[{datatable-base-border}]*/
border-width: 1px 1px 1px 0px;
border-style: solid;
border-color: #dddddd;
/*[{datatable-base-border}]*/
}
table.show-gridlines tbody td:empty {
background-color: #E3E2E6;
/*[{datatable-base-border}]*/
background-color: #e3e2e6;
/*[{datatable-base-border}]*/
}
table.show-gridlines tbody tr.selected td:empty {
background-color: #767676;
/*[{datatable-row-selected-empty-background}]*/
background-color: #767676;
/*[{datatable-row-selected-empty-background}]*/
}
table.dataTable.row-border tbody th,
table.dataTable.row-border tbody td,
table.dataTable.storage tbody th,
table.dataTable.storage tbody td {
border-top: 1px solid @BaseLight;
/*[{datatable-base-border}]*/
border-top: 1px solid @BaseLight;
/*[{datatable-base-border}]*/
}
table.dataTable.row-border tbody tr:first-child th,
table.dataTable.row-border tbody tr:first-child td,
table.dataTable.storage tbody tr:first-child th,
table.dataTable.storage tbody tr:first-child td {
border-top: none;
border-top: none;
}
table.dataTable.cell-border tbody th,
table.dataTable.cell-border tbody td {
border-top: 1px solid @BaseLight;
/*[1px solid {datatable-base-border}]*/
border-right: 1px solid @BaseLight;
/*[1px solid {datatable-base-border}]*/
border-top: 1px solid @BaseLight;
/*[1px solid {datatable-base-border}]*/
border-right: 1px solid @BaseLight;
/*[1px solid {datatable-base-border}]*/
}
table.dataTable.cell-border tbody tr th:first-child,
table.dataTable.cell-border tbody tr td:first-child {
border-left: 1px solid @BaseLight;
/*[1px solid {datatable-base-border}]*/
border-left: 1px solid @BaseLight;
/*[1px solid {datatable-base-border}]*/
}
table.dataTable.cell-border tbody tr:first-child th,
table.dataTable.cell-border tbody tr:first-child td {
border-top: none;
border-top: none;
}
table.dataTable.hover tbody tr:hover,
table.dataTable.storage tbody tr:hover {
color: #000000;
/*[{datatable-row-hover-text}]*/
background-color: #FCFCFC;
/*[{datatable-row-hover-background}]*/
color: #000000;
/*[{datatable-row-hover-text}]*/
background-color: #fcfcfc;
/*[{datatable-row-hover-background}]*/
}
table.dataTable.hover tbody tr:hover.selected,
table.dataTable.storage tbody tr:hover.selected {
background-color: #3399ff;
/*[{datatable-row-selected-background}]*/
background-color: #3399ff;
/*[{datatable-row-selected-background}]*/
}
table.dataTable.hover:not(:focus) tbody tr:hover.selected,
table.dataTable.storage:not(:focus) tbody tr:hover.selected {
background-color: #767676;
/*[{datatable-row-selected-background}]*/
background-color: #767676;
/*[{datatable-row-selected-background}]*/
}
table.dataTable.no-footer {
border-bottom: 1px solid #555;
/*[1px solid {datatable-base-border}]*/
border-bottom: 1px solid #555;
/*[1px solid {datatable-base-border}]*/
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
white-space: nowrap;
}
table.dataTable.compact thead th,
table.dataTable.compact thead td {
padding: 4px 17px 4px 4px;
padding: 4px 17px 4px 4px;
}
table.dataTable.compact tfoot th,
table.dataTable.compact tfoot td {
padding: 4px;
padding: 4px;
}
table.dataTable.compact tbody th,
table.dataTable.compact tbody td {
padding: 4px;
padding: 4px;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
white-space: nowrap;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
white-space: nowrap;
}
table.dataTable,
table.dataTable th,
table.dataTable td {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.dataTables_scrollBody .storage {
margin: 0px;
margin: 0px;
}
/*
* Control feature layout
*/
.dataTables_wrapper {
position: relative;
clear: both;
*zoom: 1;
zoom: 1;
padding: 0px 10px 0px 10px;
.flex-display();
.flex-direction();
overflow:hidden;
position: relative;
clear: both;
*zoom: 1;
zoom: 1;
padding: 0px 10px 0px 10px;
.flex-display();
.flex-direction();
overflow: hidden;
}
.dataTables_wrapper .dataTables_length {
float: left;
margin: 10px;
float: left;
margin: 10px;
}
.dataTables_wrapper .dataTables_filter {
float: right;
text-align: right;
margin: 10px;
float: right;
text-align: right;
margin: 10px;
}
.dataTables_wrapper .dataTables_filter input {
background-color: #333337;
/*[{plugin-textbox-background-color}]*/
border: 1px solid #3F3F46;
/*[1px solid {plugin-textbox-border-color}]*/
margin-left: 0.5em;
outline: none;
background-color: #333337;
/*[{plugin-textbox-background-color}]*/
border: 1px solid #3f3f46;
/*[1px solid {plugin-textbox-border-color}]*/
margin-left: 0.5em;
outline: none;
}
.dataTables_wrapper .dataTables_filter input:active,
.dataTables_wrapper .dataTables_filter input:focus,
.dataTables_wrapper .dataTables_filter input:hover {
border: 1px solid #007ACC;
/*[1px solid {search-control-mouse-over-border}]*/
border: 1px solid #007acc;
/*[1px solid {search-control-mouse-over-border}]*/
}
.dataTables_wrapper .dataTables_length select {
outline: none;
outline: none;
}
.dataTables_wrapper .dataTables_length select:active,
.dataTables_wrapper .dataTables_length select:focus,
.dataTables_wrapper .dataTables_length select:hover {
outline: 1px solid #007ACC;
/*[1px solid {search-control-mouse-over-border}]*/
outline: 1px solid #007acc;
/*[1px solid {search-control-mouse-over-border}]*/
}
.dataTables_wrapper .dataTables_info {
clear: both;
float: left;
padding-top: 0.755em;
padding-left: 10px;
clear: both;
float: left;
padding-top: 0.755em;
padding-left: 10px;
}
.dataTables_wrapper .dataTables_paginate {
float: right;
text-align: right;
padding-top: 0.45em;
float: right;
text-align: right;
padding-top: 0.45em;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
*cursor: hand;
color: @SelectionColor !important;
/*[{environment-panel-hyperlink} !important]*/
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
*cursor: hand;
color: @SelectionColor !important;
/*[{environment-panel-hyperlink} !important]*/
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current,
.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
color: black !important;
/*[{environment-panel-hyperlink-disabled} !important]*/
color: black !important;
/*[{environment-panel-hyperlink-disabled} !important]*/
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
cursor: default;
color: white !important;
/*[{environment-panel-hyperlink-disabled} !important]*/
border: 1px solid transparent;
background: transparent;
box-shadow: none;
cursor: default;
color: white !important;
/*[{environment-panel-hyperlink-disabled} !important]*/
border: 1px solid transparent;
background: transparent;
box-shadow: none;
}
.paginate_button.disabled {
visibility: hidden;
visibility: hidden;
}
.dataTables_wrapper .dataTables_paginate .ellipsis {
padding: 0 1em;
padding: 0 1em;
}
.dataTables_wrapper .dataTables_processing {
position: fixed;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
margin-top: -25px;
padding-top: 20px;
background-color: transparent;
z-index: 10000;
position: fixed;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
margin-top: -25px;
padding-top: 20px;
background-color: transparent;
z-index: 10000;
}
.dataTables_wrapper .dataTables_length,
@@ -539,77 +537,75 @@ table.dataTable td {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #000000;
/*[{common-controls-inner-tab-inactive-text}]*/
color: #000000;
/*[{common-controls-inner-tab-inactive-text}]*/
}
.dataTables_wrapper .dataTables_scroll {
clear: both;
width:100%;
overflow: hidden;
.flex-display();
.flex-direction();
clear: both;
width: 100%;
overflow: hidden;
.flex-display();
.flex-direction();
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th>div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td>div.dataTables_sizing {
height: 0;
margin: 0 !important;
padding: 0 !important;
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td > div.dataTables_sizing {
height: 0;
margin: 0 !important;
padding: 0 !important;
}
.dataTables_wrapper.no-footer .dataTables_scrollHead {
height: 50px;
height: 50px;
}
.dataTables_wrapper.no-footer .dataTables_scrollBody {
border-bottom: none;
border-bottom: none;
}
.dataTables_wrapper.no-footer div.dataTables_scrollHead table,
.dataTables_wrapper.no-footer div.dataTables_scrollBody table {
border-bottom: none;
border-bottom: none;
}
.dataTables_wrapper:after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
.dataTables_wrapper a {
color: whitesmoke;
/*[{common-controls-inner-tab-inactive-background}]*/
color: whitesmoke;
/*[{common-controls-inner-tab-inactive-background}]*/
}
tr td.nameColumnText {
color: #000000;
/*[{plugin-textbox-color}]*/
color: #000000;
/*[{plugin-textbox-color}]*/
}
tr td.nameColumnText.selected,
tr:hover td.nameColumnText {
color: @BaseLight !important;
/*[{plugin-treeview-content-selected-color} !important]*/
color: @BaseLight !important;
/*[{plugin-treeview-content-selected-color} !important]*/
}
.textRight {
text-align: right;
text-align: right;
}
.dataTables_filter {
display: none;
display: none;
}
/*@media screen and (max-width: 767px) {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
@@ -633,21 +629,21 @@ tr:hover td.nameColumnText {
}*/
.context-menu-item.icon-reset-column-order {
background-image: url(../../images/Reset-column-options.svg);
background-image: url(/images/Reset-column-options.svg);
}
.context-menu-item.icon-shift-non-empty-columns-left {
background-image: url(../../images/Reorder.svg);
background-image: url(/images/Reorder.svg);
}
.context-menu-item.icon-edit-entity {
background-image: url(../../images/Edit_entity.svg);
background-image: url(/images/Edit_entity.svg);
}
.context-menu-item.icon-delete-entity {
background-image: url(../../images/delete.svg);
background-image: url(/images/delete.svg);
}
.context-menu-item.icon-customize-columns {
background-image: url(../../images/Options.svg);
}
background-image: url(/images/Options.svg);
}

View File

@@ -724,24 +724,45 @@ execute-sproc-params-pane {
.results-container,
.errors-container {
padding: @MediumSpace 0px 0px @MediumSpace;
height: 100%;
.flex-display();
.flex-direction();
overflow: hidden;
.enterInputParameters {
padding: @LargeSpace @MediumSpace;
.toggles {
height: @ToggleHeight;
width: @ToggleWidth;
margin-left: @MediumSpace;
&:focus {
.focus();
}
.tab {
margin-right: @MediumSpace;
}
.toggleSwitch {
.toggleSwitch();
}
.selectedToggle {
.selectedToggle();
}
.unselectedToggle {
.unselectedToggle();
}
}
div[role="tabpanel"] {
height: 100%;
padding-bottom: 50px;
.enterInputParameters {
padding: @LargeSpace @MediumSpace;
}
}
.errors-container {
padding-left: (2 * @MediumSpace);
padding: @MediumSpace 0px 0px @MediumSpace;
.errors-header {
font-weight: 700;
font-size: @DefaultFontSize;
@@ -1292,7 +1313,7 @@ menuQuickStart {
}
.plusimg-but {
background-image: url(../images/plus_normal.svg);
background-image: url(/images/plus_normal.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1300,7 +1321,7 @@ menuQuickStart {
}
.plusimg-but:hover {
background-image: url(../images/plus_hover.svg);
background-image: url(/images/plus_hover.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1308,7 +1329,7 @@ menuQuickStart {
}
.plusimg-but:active {
background-image: url(../images/plus_pressed.svg);
background-image: url(/images/plus_pressed.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1316,7 +1337,7 @@ menuQuickStart {
}
.plusimg-but:disabled {
background-image: url(../images/plus_disabled.svg);
background-image: url(/images/plus_disabled.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1324,7 +1345,7 @@ menuQuickStart {
}
.minusimg-but {
background-image: url(../images/minus_normal.svg);
background-image: url(/images/minus_normal.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1332,7 +1353,7 @@ menuQuickStart {
}
.minusimg-but:hover {
background-image: url(../images/minus_hover.svg);
background-image: url(/images/minus_hover.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1340,7 +1361,7 @@ menuQuickStart {
}
.minusimg-but:active {
background-image: url(../images/minus_pressed.svg);
background-image: url(/images/minus_pressed.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -1348,7 +1369,7 @@ menuQuickStart {
}
.minusimg-but:disabled {
background-image: url(../images/minus_disabled.svg);
background-image: url(/images/minus_disabled.svg);
background-repeat: no-repeat;
padding: 6px 16px;
position: static;
@@ -2686,7 +2707,7 @@ a:link {
.errorIcon {
width: @ErrorIconWidth;
height: @LoadingErrorIconSize;
background-image: url(../images/error_no_outline.svg);
background-image: url(/images/error_no_outline.svg);
background-repeat: no-repeat;
background-position: center;
background-size: 3px;
@@ -3067,11 +3088,4 @@ settings-pane {
.hiddenMain {
display: none;
height: 0px;
}
.spinner {
width: 100%;
position: absolute;
z-index: 1;
background: white;
height: 100%;
}
}

View File

@@ -200,12 +200,4 @@
.migration:disabled {
background-color: #ccc;
}
.trigger-field {
width: 40%;
margin-top: 10px
}
.trigger-form {
padding: 10px 30px 10px 30px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

10596
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,10 +9,10 @@
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
"@babel/plugin-proposal-class-properties": "7.13.0",
"@babel/plugin-proposal-decorators": "7.14.2",
"@fluentui/react": "8.14.8",
"@azure/msal-browser": "2.14.2",
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.14.3",
"@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1",
@@ -22,7 +22,7 @@
"@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1",
"@nteract/editor": "10.1.12",
"@nteract/editor": "10.1.2",
"@nteract/fixtures": "2.3.0",
"@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0",
@@ -41,17 +41,13 @@
"@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3",
"@testing-library/jest-dom": "5.11.9",
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"applicationinsights": "1.8.0",
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19",
"clipboard-copy": "4.0.1",
"copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2",
"css-element-queries": "1.1.1",
"crypto-browserify": "3.12.0",
"css-element-queries": "1.2.3",
"d3": "6.1.1",
"datatables.net-colreorder-dt": "1.5.1",
"datatables.net-dt": "1.10.19",
@@ -59,18 +55,18 @@
"dayjs": "1.8.19",
"dom-to-image": "2.6.0",
"dotenv": "8.2.0",
"eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-jest": "24.3.6",
"eslint-plugin-react": "7.23.2",
"hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23",
"html2canvas": "1.0.0-rc.7",
"i18next": "20.2.2",
"i18next-browser-languagedetector": "6.1.0",
"i18next-http-backend": "1.2.2",
"iframe-resizer-react": "1.1.0",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.5.1",
"jquery-typeahead": "2.10.6",
"jquery": "3.6.0",
"jquery-typeahead": "2.11.1",
"jquery-ui-dist": "1.12.1",
"knockout": "3.5.1",
"mkdirp": "1.0.4",
@@ -80,110 +76,117 @@
"plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42",
"q": "1.5.1",
"react": "16.13.1",
"react": "17.0.2",
"react-animate-height": "2.0.8",
"react-dnd": "9.4.0",
"react-dnd-html5-backend": "9.4.0",
"react-dom": "16.13.1",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0",
"react-dom": "17.0.2",
"react-hotkeys": "2.0.0",
"react-i18next": "11.8.5",
"react-i18next": "11.8.15",
"react-notification-system": "0.2.17",
"react-redux": "7.1.3",
"react-splitter-layout": "4.0.0",
"redux": "4.0.4",
"react-redux": "7.2.4",
"redux": "4.1.0",
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12",
"rxjs": "6.6.3",
"sanitize-html": "2.3.3",
"styled-components": "4.3.2",
"styled-components": "5.3.0",
"swr": "0.4.0",
"terser-webpack-plugin": "3.1.0",
"underscore": "1.9.1",
"underscore": "1.13.1",
"utility-types": "3.10.0",
"zustand": "3.5.0"
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.0",
"@babel/preset-react": "7.9.4",
"@babel/preset-typescript": "7.9.0",
"@testing-library/react": "11.2.3",
"@babel/core": "7.14.2",
"@babel/preset-env": "7.14.2",
"@babel/preset-react": "7.13.13",
"@babel/preset-typescript": "7.13.0",
"@svgr/webpack": "5.5.0",
"@testing-library/jest-dom": "5.12.0",
"@testing-library/react": "11.2.7",
"@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56",
"@types/crossroads": "0.0.30",
"@types/d3": "5.9.2",
"@types/dom-to-image": "2.6.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/enzyme": "3.10.8",
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/node": "12.11.1",
"@types/memoize-one": "4.1.1",
"@types/mkdirp": "1.0.1",
"@types/node": "15.3.0",
"@types/node-fetch": "2.5.10",
"@types/post-robot": "10.0.1",
"@types/promise.prototype.finally": "2.0.4",
"@types/jest": "26.0.20",
"@types/q": "1.5.1",
"@types/react": "17.0.3",
"@types/react-dom": "17.0.3",
"@types/react": "17.0.5",
"@types/react-dom": "17.0.5",
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1",
"@types/sanitize-html": "1.27.2",
"@types/react-redux": "7.1.16",
"@types/sanitize-html": "2.3.1",
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"babel-jest": "24.9.0",
"babel-loader": "8.1.0",
"@types/styled-components": "5.1.9",
"@types/underscore": "1.11.2",
"@typescript-eslint/eslint-plugin": "4.23.0",
"@typescript-eslint/parser": "4.23.0",
"@wojtekmaj/enzyme-adapter-react-17": "0.6.1",
"babel-jest": "26.6.3",
"babel-loader": "8.2.2",
"buffer": "5.1.0",
"case-sensitive-paths-webpack-plugin": "2.3.0",
"case-sensitive-paths-webpack-plugin": "2.4.0",
"clean-webpack-plugin": "4.0.0-alpha.0",
"copy-webpack-plugin": "8.1.1",
"create-file-webpack": "1.0.2",
"css-loader": "1.0.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.5",
"enzyme-to-json": "3.6.1",
"eslint": "7.8.1",
"enzyme-to-json": "3.6.2",
"eslint": "7.26.0",
"eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-react-hooks": "4.2.0",
"expose-loader": "2.0.0",
"expect-playwright": "0.3.3",
"fast-glob": "3.2.5",
"file-loader": "2.0.0",
"file-loader": "6.2.0",
"fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.0",
"html-loader": "0.5.5",
"html-loader": "2.1.2",
"html-loader-jest": "0.2.1",
"html-webpack-plugin": "4.5.2",
"jest": "25.5.4",
"html-webpack-plugin": "5.3.1",
"jest": "26.6.3",
"jest-canvas-mock": "2.1.0",
"jest-playwright-preset": "1.5.1",
"jest-trx-results-processor": "0.0.7",
"less": "3.8.1",
"less-loader": "4.1.0",
"jest-playwright-preset": "1.5.2",
"jest-trx-results-processor": "2.2.0",
"less": "4.1.1",
"less-loader": "8.1.1",
"less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3",
"mini-css-extract-plugin": "1.6.0",
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1",
"playwright": "1.10.0",
"prettier": "2.2.1",
"raw-loader": "0.5.1",
"prettier": "2.3.0",
"process": "0.11.10",
"raw-loader": "4.0.2",
"react-dev-utils": "11.0.4",
"rimraf": "3.0.0",
"sinon": "3.2.1",
"style-loader": "0.23.0",
"ts-loader": "6.2.2",
"tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0",
"style-loader": "2.0.0",
"terser-webpack-plugin": "5.1.2",
"ts-loader": "9.1.2",
"tslint": "5.20.1",
"tslint-microsoft-contrib": "6.2.0",
"typedoc": "0.20.36",
"typescript": "4.3.4",
"url-loader": "1.1.1",
"typescript": "4.2.4",
"url-loader": "4.1.1",
"wait-on": "4.0.2",
"webpack": "4.46.0",
"webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "3.3.10",
"webpack-dev-server": "3.11.0"
"webpack": "5.37.0",
"webpack-bundle-analyzer": "4.4.1",
"webpack-cli": "4.7.0",
"webpack-dev-server": "3.11.2"
},
"scripts": {
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
"start": "webpack serve",
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
"build:dataExplorer:ci": "npm run build:ci",
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
@@ -227,4 +230,4 @@
"prettier": {
"printWidth": 120
}
}
}

View File

@@ -1,4 +1,3 @@
{
"PROXY_PATH": "/proxy",
"msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
"PROXY_PATH": "/proxy"
}

View File

@@ -62,17 +62,6 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
})
.catch(() => res.sendStatus(500));
});
app.get("/", (req, res) => {
fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
.then((response) => response.json())
.then(({ commit: { sha } }) => {
const explorer = new URL(
"https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/hostedExplorer.html"
);
return res.redirect(explorer.href);
})
.catch(() => res.sendStatus(500));
});
app.listen(port, () => {
console.log(`Example app listening on port: ${port}`);

View File

@@ -1,5 +1,5 @@
import arrowLeftImg from "images/imgarrowlefticon.svg";
import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import { userContext } from "../UserContext";
export interface CollapsedResourceTreeProps {

View File

@@ -158,6 +158,16 @@ export class DocumentsGridMetrics {
public static DocumentEditorMaxWidthRatio: number = 0.4;
}
export class ExplorerMetrics {
public static SplitterMinWidth: number = 240;
public static SplitterMaxWidth: number = 400;
public static CollapsedResourceTreeWidth: number = 36;
}
export class SplitterMetrics {
public static CollapsedPositionLeft: number = ExplorerMetrics.CollapsedResourceTreeWidth;
}
export class Areas {
public static ResourceTree: string = "Resource Tree";
public static ContextualPane: string = "Contextual Pane";

View File

@@ -83,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
if (_client) return _client;
const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey,
...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
tokenProvider,
connectionPolicy: {
enableEndpointDiscovery: false,

View File

@@ -5,6 +5,7 @@ import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId";
import { updateUserContext } from "../UserContext";
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB";
@@ -30,7 +31,7 @@ const collection = {
},
} as Collection;
const documentId = ({
const documentId = {
partitionKeyHeader: () => "[]",
self: "db/testDB/db/testCollection/docs/testId",
partitionKeyProperty,
@@ -39,7 +40,7 @@ const documentId = ({
kind: "Hash",
version: 1,
},
} as unknown) as DocumentId;
} as unknown as DocumentId;
const databaseAccount = {
id: "foo",

View File

@@ -111,7 +111,7 @@ export function queryDocuments(
headers: response.headers,
};
}
await errorHandling(response, "querying documents", params);
errorHandling(response, "querying documents", params);
return undefined;
});
}
@@ -153,11 +153,11 @@ export function readDocument(
),
},
})
.then(async (response) => {
.then((response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "reading document", params);
return errorHandling(response, "reading document", params);
});
}
@@ -192,11 +192,11 @@ export function createDocument(
...authHeaders(),
},
})
.then(async (response) => {
.then((response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "creating document", params);
return errorHandling(response, "creating document", params);
});
}
@@ -238,11 +238,11 @@ export function updateDocument(
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
},
})
.then(async (response) => {
.then((response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "updating document", params);
return errorHandling(response, "updating document", params);
});
}
@@ -278,11 +278,11 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
},
})
.then(async (response) => {
.then((response) => {
if (response.ok) {
return undefined;
}
return await errorHandling(response, "deleting document", params);
return errorHandling(response, "deleting document", params);
});
}
@@ -314,7 +314,7 @@ export function createMongoCollectionWithProxy(
return window
.fetch(
`${endpoint}/createCollection?${queryString.stringify(
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
mongoParams as unknown as queryString.ParsedUrlQueryInput
)}`,
{
method: "POST",
@@ -325,11 +325,11 @@ export function createMongoCollectionWithProxy(
},
}
)
.then(async (response) => {
.then((response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "creating collection", mongoParams);
return errorHandling(response, "creating collection", mongoParams);
});
}

View File

@@ -1,17 +1,19 @@
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId";
import { useDatabases } from "../Explorer/useDatabases";
import { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as QueryUtils from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { createCollection } from "./dataAccess/createCollection";
import { createDocument } from "./dataAccess/createDocument";
import { deleteDocument } from "./dataAccess/deleteDocument";
import { queryDocuments } from "./dataAccess/queryDocuments";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { handleError } from "./ErrorHandlingUtils";
export class QueriesClient {
@@ -98,35 +100,45 @@ export class QueriesClient {
const options: any = { enableCrossPartitionQuery: true };
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
const results = await queryDocuments(
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
SavedQueries.DatabaseName,
SavedQueries.CollectionName,
this.fetchQueriesQuery(),
options
).fetchAll();
let queries: DataModels.Query[] = _.map(results.resources, (document: DataModels.Query) => {
if (!document) {
return undefined;
}
const { id, resourceId, query, queryName } = document;
const parsedQuery: DataModels.Query = {
resourceId: resourceId,
queryName: queryName,
query: query,
id: id,
};
try {
this.validateQuery(parsedQuery);
return parsedQuery;
} catch (error) {
return undefined;
}
});
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
clearMessage();
return queries;
);
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
return QueryUtils.queryAllPages(fetchQueries)
.then(
(results: ViewModels.QueryResults) => {
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
if (!document) {
return undefined;
}
const { id, resourceId, query, queryName } = document;
const parsedQuery: DataModels.Query = {
resourceId: resourceId,
queryName: queryName,
query: query,
id: id,
};
try {
this.validateQuery(parsedQuery);
return parsedQuery;
} catch (error) {
return undefined;
}
});
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
return Promise.resolve(queries);
},
(error: any) => {
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
return Promise.reject(error);
}
)
.finally(() => clearMessage());
}
public async deleteQuery(query: DataModels.Query): Promise<void> {
@@ -177,7 +189,7 @@ export class QueriesClient {
private findQueriesCollection(): ViewModels.Collection {
const queriesDatabase: ViewModels.Database = _.find(
useDatabases.getState().databases,
this.container.databases(),
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
);
if (!queriesDatabase) {

View File

@@ -1,6 +1,6 @@
import arrowLeftImg from "images/imgarrowlefticon.svg";
import refreshImg from "images/refresh-cosmos.svg";
import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import refreshImg from "../../images/refresh-cosmos.svg";
import { AuthType } from "../AuthType";
import { userContext } from "../UserContext";

View File

@@ -1,3 +1,7 @@
import * as ko from "knockout";
import { SplitterMetrics } from "./Constants";
export enum SplitterDirection {
Horizontal = "horizontal",
Vertical = "vertical",
@@ -24,12 +28,14 @@ export class Splitter {
public lastX!: number;
public lastWidth!: number;
private isCollapsed: ko.Observable<boolean>;
private bounds: SplitterBounds;
private direction: SplitterDirection;
constructor(options: SplitterOptions) {
this.splitterId = options.splitterId;
this.leftSideId = options.leftId;
this.isCollapsed = ko.observable<boolean>(false);
this.bounds = options.bounds;
this.direction = options.direction;
this.initialize();
@@ -77,4 +83,23 @@ export class Splitter {
};
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
public collapseLeft() {
this.lastX = $(this.splitter).position().left;
this.lastWidth = $(this.leftSide).width();
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
$(this.leftSide).css("width", "");
$(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible
$(this.splitter).removeClass("ui-resizable-e");
this.isCollapsed(true);
}
public expandLeft() {
$(this.splitter).addClass("ui-resizable-e");
$(this.leftSide).css("width", this.lastWidth);
$(this.splitter).css("left", this.lastX);
$(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing
$(this.leftSide).resizable("enable");
this.isCollapsed(false);
}
}

View File

@@ -1,3 +1,5 @@
import DeleteIcon from "images/delete.svg";
import EditIcon from "images/Edit_entity.svg";
import {
Dropdown,
IDropdownOption,
@@ -10,8 +12,6 @@ import {
TooltipHost,
} from "@fluentui/react";
import React, { FunctionComponent } from "react";
import DeleteIcon from "../../images/delete.svg";
import EditIcon from "../../images/Edit_entity.svg";
import { CassandraType, TableType } from "../Explorer/Tables/Constants";
import { userContext } from "../UserContext";
import { EntityValue } from "./EntityValue";

View File

@@ -1,6 +1,6 @@
import { Image, Stack, TextField } from "@fluentui/react";
import FolderIcon from "images/folder_16x16.svg";
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
import FolderIcon from "../../../images/folder_16x16.svg";
import * as Constants from "../Constants";
import { InfoTooltip } from "../Tooltip/InfoTooltip";

View File

@@ -54,7 +54,7 @@ export async function createTrigger(
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.triggers.create((trigger as unknown) as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type
.scripts.triggers.create(trigger as unknown as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type
return response.resource;
} catch (error) {
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);

View File

@@ -405,7 +405,7 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
const sdkResponse = await client()
.offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options);
.replace(newOffer as unknown as OfferDefinition, options);
return parseSDKOfferResponse(sdkResponse);
};

View File

@@ -51,7 +51,7 @@ export async function updateTrigger(
.database(databaseId)
.container(collectionId)
.scripts.trigger(trigger.id)
.replace((trigger as unknown) as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type
.replace(trigger as unknown as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type
return response?.resource;
} catch (error) {
handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);

View File

@@ -28,7 +28,6 @@ export interface ConfigContext {
armAPIVersion?: string;
allowedJunoOrigins: string[];
enableSchemaAnalyzer: boolean;
msalRedirectURI?: string;
}
// Default configuration
@@ -120,14 +119,6 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
const armAPIVersion = params.get("armAPIVersion") || "";
updateConfigContext({ armAPIVersion });
}
if (params.has("armEndpoint")) {
const ARM_ENDPOINT = params.get("armEndpoint") || "";
updateConfigContext({ ARM_ENDPOINT });
}
if (params.has("aadEndpoint")) {
const AAD_ENDPOINT = params.get("aadEndpoint") || "";
updateConfigContext({ AAD_ENDPOINT });
}
if (params.has("platform")) {
const platform = params.get("platform");
switch (platform) {

View File

@@ -3,10 +3,11 @@ import {
Resource,
StoredProcedureDefinition,
TriggerDefinition,
UserDefinedFunctionDefinition
UserDefinedFunctionDefinition,
} from "@azure/cosmos";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer/Explorer";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId";
@@ -89,6 +90,7 @@ export interface Database extends TreeNode {
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
selectDatabase(): void;
expandDatabase(): Promise<void>;
collapseDatabase(): void;
@@ -274,6 +276,8 @@ export interface TabOptions {
tabKind: CollectionTabKind;
title: string;
tabPath: string;
hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number;
@@ -284,7 +288,6 @@ export interface TabOptions {
rid?: string;
node?: TreeNode;
theme?: string;
index?: number;
}
export interface DocumentsTabOptions extends TabOptions {
@@ -370,7 +373,6 @@ export enum TerminalKind {
Default = 0,
Mongo = 1,
Cassandra = 2,
PostgreSQL = 3
}
export interface DataExplorerInputsFrame {

View File

@@ -0,0 +1,172 @@
import AddCollectionIcon from "images/AddCollection.svg";
import AddSqlQueryIcon from "images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "images/AddStoredProcedure.svg";
import AddTriggerIcon from "images/AddTrigger.svg";
import AddUdfIcon from "images/AddUdf.svg";
import DeleteCollectionIcon from "images/DeleteCollection.svg";
import DeleteDatabaseIcon from "images/DeleteDatabase.svg";
import DeleteSprocIcon from "images/DeleteSproc.svg";
import DeleteTriggerIcon from "images/DeleteTrigger.svg";
import DeleteUDFIcon from "images/DeleteUDF.svg";
import HostedTerminalIcon from "images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
export interface CollectionContextMenuButtonParams {
databaseId: string;
collectionId: string;
}
export interface DatabaseContextMenuButtonParams {
databaseId: string;
}
/**
* New resource tree (in ReactJS)
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(databaseId),
label: `New ${getCollectionName()}`,
},
];
if (userContext.apiType !== "Tables") {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () => container.openDeleteDatabaseConfirmationPane(),
label: `Delete ${getDatabaseName()}`,
styleClass: "deleteDatabaseMenuItem",
});
}
return items;
}
public static createCollectionContextMenuButton(
container: Explorer,
selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [];
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
label: "New SQL Query",
});
}
if (userContext.apiType === "Mongo") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
label: "New Query",
});
items.push({
iconSrc: HostedTerminalIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
if (container.isShellEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
selectedCollection && selectedCollection.onNewMongoShellClick();
}
},
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
});
}
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: AddStoredProcedureIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
label: "New Stored Procedure",
});
items.push({
iconSrc: AddUdfIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
},
label: "New UDF",
});
items.push({
iconSrc: AddTriggerIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
},
label: "New Trigger",
});
}
items.push({
iconSrc: DeleteCollectionIcon,
onClick: () => container.openDeleteCollectionConfirmationPane(),
label: `Delete ${getCollectionName()}`,
styleClass: "deleteCollectionMenuItem",
});
return items;
}
public static createStoreProcedureContextMenuItems(
container: Explorer,
storedProcedure: StoredProcedure
): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(),
label: "Delete Store Procedure",
},
];
}
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(),
label: "Delete Trigger",
},
];
}
public static createUserDefinedFunctionContextMenuItems(
container: Explorer,
userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function",
},
];
}
}

View File

@@ -1,182 +0,0 @@
import React from "react";
import AddCollectionIcon from "../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
import AddTriggerIcon from "../../images/AddTrigger.svg";
import AddUdfIcon from "../../images/AddUdf.svg";
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels";
import { useSidePanel } from "../hooks/useSidePanel";
import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer";
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import { useSelectedNode } from "./useSelectedNode";
export interface CollectionContextMenuButtonParams {
databaseId: string;
collectionId: string;
}
export interface DatabaseContextMenuButtonParams {
databaseId: string;
}
/**
* New resource tree (in ReactJS)
*/
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(databaseId),
label: `New ${getCollectionName()}`,
},
];
if (userContext.apiType !== "Tables") {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () =>
useSidePanel
.getState()
.openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
label: `Delete ${getDatabaseName()}`,
styleClass: "deleteDatabaseMenuItem",
});
}
return items;
};
export const createCollectionContextMenuButton = (
container: Explorer,
selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] => {
const items: TreeNodeMenuItem[] = [];
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined),
label: "New SQL Query",
});
}
if (userContext.apiType === "Mongo") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined),
label: "New Query",
});
items.push({
iconSrc: HostedTerminalIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
if (container.isShellEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
selectedCollection && selectedCollection.onNewMongoShellClick();
}
},
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
});
}
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: AddStoredProcedureIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
},
label: "New Stored Procedure",
});
items.push({
iconSrc: AddUdfIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined);
},
label: "New UDF",
});
items.push({
iconSrc: AddTriggerIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined);
},
label: "New Trigger",
});
}
items.push({
iconSrc: DeleteCollectionIcon,
onClick: () =>
useSidePanel
.getState()
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={container} />),
label: `Delete ${getCollectionName()}`,
styleClass: "deleteCollectionMenuItem",
});
return items;
};
export const createStoreProcedureContextMenuItems = (
container: Explorer,
storedProcedure: StoredProcedure
): TreeNodeMenuItem[] => {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(),
label: "Delete Store Procedure",
},
];
};
export const createTriggerContextMenuItems = (container: Explorer, trigger: Trigger): TreeNodeMenuItem[] => {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(),
label: "Delete Trigger",
},
];
};
export const createUserDefinedFunctionContextMenuItems = (
container: Explorer,
userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] => {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function",
},
];
};

View File

@@ -2,10 +2,10 @@
* Accordion top class
*/
import TriangleDownIcon from "images/Triangle-down.svg";
import TriangleRightIcon from "images/Triangle-right.svg";
import * as React from "react";
import AnimateHeight from "react-animate-height";
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import * as Constants from "../../../Common/Constants";
export interface AccordionComponentProps {}

View File

@@ -6,8 +6,8 @@
* - calling render()
*/
import LeftArrowIcon from "images/imgarrowlefticon.svg";
import * as React from "react";
import LeftArrowIcon from "../../../../images/imgarrowlefticon.svg";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
export interface CollapsiblePanelProps {

View File

@@ -1,8 +1,8 @@
import CollapseChevronDownIcon from "images/QueryBuilder/CollapseChevronDown_16x.png";
/**
* React component for Command button component.
*/
import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -204,11 +204,9 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
}}
>
<div className="commandDropdown">
{this.props.children.map(
(c: CommandButtonComponentProps, index: number): JSX.Element => {
return CommandButtonComponent.renderButton(c, `${index}`);
}
)}
{this.props.children.map((c: CommandButtonComponentProps, index: number): JSX.Element => {
return CommandButtonComponent.renderButton(c, `${index}`);
})}
</div>
</div>
</div>

View File

@@ -0,0 +1,69 @@
/**
* React component for Switch Directory
*/
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
import * as React from "react";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DefaultDirectoryDropdownProps {
directories: Array<Tenant>;
defaultDirectoryId: string;
onDefaultDirectoryChange: (newDirectory: Tenant) => void;
}
export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDirectoryDropdownProps> {
public static readonly lastVisitedKey: string = "lastVisited";
public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = {
key: DefaultDirectoryDropdownComponent.lastVisitedKey,
text: "Sign in to your last visited directory",
};
const directoryOptions: Array<IDropdownOption> = this.props.directories.map((dirc): IDropdownOption => {
return {
key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})`,
};
});
const dropDownOptions: Array<IDropdownOption> = [lastVisitedOption, ...directoryOptions];
const dropDownProps: IDropdownProps = {
label: "Set your default directory",
options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown",
};
return <Dropdown {...dropDownProps} />;
}
private _onDropdownChange = (e: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void => {
if (!option || !option.key) {
return;
}
if (option.key === this.props.defaultDirectoryId) {
return;
}
if (option.key === DefaultDirectoryDropdownComponent.lastVisitedKey) {
this.props.onDefaultDirectoryChange({
tenantId: option.key,
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined,
});
return;
}
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
if (!selectedDirectory) {
return;
}
this.props.onDefaultDirectoryChange(selectedDirectory);
};
}

View File

@@ -0,0 +1,35 @@
<div class="dynamicList" data-bind="setTemplateReady: true">
<div class="dynamicListContainer" data-bind="foreach: listItems">
<div class="dynamicListItem">
<input
id="uniqueKeyItems"
type="text"
autocomplete="off"
data-bind="value: value, attr: {placeholder: $parent.placeholder, 'aria-label': $parent.ariaLabel}"
/>
<span
class="dynamicListItemDelete"
title="Remove item"
role="button"
aria-label="Remove item"
tabindex="0"
data-bind="click: $parent.removeItem, event: { keydown: $parent.onRemoveItemKeyPress }"
>
<img src="/images/delete.svg" alt="Remove item" />
</span>
</div>
</div>
<div class="dynamicListItemNew">
<span
class="dynamicListItemAdd"
id="addUniqueKeyBtn"
role="button"
aria-label="Add unique key"
tabindex="0"
data-bind="click: addItem, event: { keydown: onAddItemKeyPress }"
>
<img src="/images/Add-property.svg" data-bind="attr: {alt: buttonText}" />
<span data-bind="text: buttonText"></span>
</span>
</div>
</div>

View File

@@ -1,11 +1,6 @@
import { Spinner, SpinnerSize } from "@fluentui/react";
import * as React from "react";
import { loadMonaco, monaco } from "../../LazyMonaco";
// import "./EditorReact.less";
interface EditorReactStates {
showEditor: boolean;
}
export interface EditorReactProps {
language: string;
content: string;
@@ -17,26 +12,22 @@ export interface EditorReactProps {
theme?: string; // Monaco editor theme
}
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
export class EditorReact extends React.Component<EditorReactProps> {
private rootNode: HTMLElement;
private editor: monaco.editor.IStandaloneCodeEditor;
private selectionListener: monaco.IDisposable;
public constructor(props: EditorReactProps) {
super(props);
this.state = {
showEditor: false,
};
}
public componentDidMount(): void {
this.createEditor(this.configureEditor.bind(this));
}
public componentDidUpdate(previous: EditorReactProps) {
if (this.props.content !== previous.content) {
this.editor.setValue(this.props.content);
}
public shouldComponentUpdate(): boolean {
// Prevents component re-rendering
return false;
}
public componentWillUnmount(): void {
@@ -44,12 +35,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
}
public render(): JSX.Element {
return (
<React.Fragment>
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />}
<div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />
</React.Fragment>
);
return <div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />;
}
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
@@ -90,12 +76,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
this.rootNode.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.rootNode, options));
if (this.rootNode.innerHTML) {
this.setState({
showEditor: true,
});
}
}
private setRef(element: HTMLElement): void {

View File

@@ -0,0 +1,6 @@
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<div class="warningErrorContent">
<span><img src="/images/error_red.svg" alt="Error" /></span>
<span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span>
</div>
</div>

View File

@@ -0,0 +1,20 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
export class GitHubReposComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GitHubReposComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GitHubReposComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -111,9 +111,7 @@ describe("NotebookTerminalComponent", () => {
const terminal: NotebookTerminalComponent = createTerminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([["terminal", "true"]])
);
expect(params).toEqual(new Map<string, string>([["terminal", "true"]]));
});
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {

View File

@@ -18,8 +18,8 @@ import {
Text,
TooltipHost,
} from "@fluentui/react";
import CosmosDBLogo from "images/CosmosDB-logo.svg";
import React, { FunctionComponent, useState } from "react";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { IGalleryItem } from "../../../../Juno/JunoClient";
import * as FileSystemUtil from "../../../Notebook/FileSystemUtil";

View File

@@ -2,12 +2,12 @@
* Wrapper around Notebook metadata
*/
import { FontWeights, Icon, IconButton, Link, Persona, PersonaSize, PrimaryButton, Stack, Text } from "@fluentui/react";
import CosmosDBLogo from "images/CosmosDB-logo.svg";
import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient";
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
import { InfoComponent } from "../NotebookGallery/InfoComponent/InfoComponent";
import "./NotebookViewerComponent.less";
export interface NotebookMetadataComponentProps {
data: IGalleryItem;

View File

@@ -43,7 +43,8 @@ interface NotebookViewerComponentState {
export class NotebookViewerComponent
extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
implements DialogHost {
implements DialogHost
{
private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@@ -55,7 +56,7 @@ export class NotebookViewerComponent
databaseAccountName: undefined,
defaultExperience: "NotebookViewer",
isReadOnly: true,
cellEditorType: "codemirror",
cellEditorType: "monaco",
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
});

View File

@@ -19,9 +19,9 @@ import {
SelectionZone,
TextField,
} from "@fluentui/react";
import SaveQueryBannerIcon from "images/save_query_banner.png";
import * as React from "react";
import * as _ from "underscore";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import * as Constants from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";

View File

@@ -38,6 +38,8 @@ describe("SettingsComponent", () => {
title: "Scale & Settings",
tabPath: "",
node: undefined,
hashLocation: "settings",
onUpdateTabsButtons: undefined,
}),
};
@@ -126,6 +128,7 @@ describe("SettingsComponent", () => {
isDatabaseExpanded: undefined,
isDatabaseShared: ko.computed(() => true),
selectedSubnodeKind: undefined,
selectDatabase: undefined,
expandDatabase: undefined,
collapseDatabase: undefined,
loadCollections: undefined,

View File

@@ -1,7 +1,7 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
import DiscardIcon from "images/discard.svg";
import SaveIcon from "images/save-cosmos.svg";
import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
@@ -16,9 +16,8 @@ import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/T
import { userContext } from "../../../UserContext";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import Explorer from "../../Explorer";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
@@ -110,7 +109,6 @@ export interface SettingsComponentState {
initialNotification: DataModels.Notification;
selectedTab: SettingsV2TabTypes;
offerLoaded: boolean;
}
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
@@ -123,6 +121,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private collection: ViewModels.Collection;
private database: ViewModels.Database;
private offer: DataModels.Offer;
private container: Explorer;
private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean;
private shouldShowIndexingPolicyEditor: boolean;
@@ -134,6 +133,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
if (this.isCollectionSettingsTab) {
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
this.container = this.collection?.container;
this.offer = this.collection?.offer();
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
@@ -145,6 +145,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
} else {
this.database = this.props.settingsTab.database;
this.container = this.database?.container;
this.offer = this.database?.offer();
}
@@ -195,7 +196,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
initialNotification: undefined,
selectedTab: SettingsV2TabTypes.ScaleTab,
offerLoaded: !!this.offer,
};
this.saveSettingsButton = {
@@ -217,19 +217,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if (this.isCollectionSettingsTab) {
this.refreshIndexTransformationProgress();
this.loadMongoIndexes();
this.loadCollectionOffer();
}
this.setAutoPilotStates();
this.setBaseline();
if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
}
}
componentDidUpdate(): void {
if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
}
}
@@ -294,7 +293,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
public hasConflictResolution = (): boolean =>
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
@@ -372,34 +371,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
});
};
private async loadCollectionOffer() {
try {
this.props.settingsTab.isExecuting(true);
await this.collection.loadOffer();
this.props.settingsTab.tabTitle(this.collection.offer() ? "Settings" : "Scale & Settings");
this.setState({ offerLoaded: true });
} catch (error) {
this.props.settingsTab.isExecutionError(true);
const errorMessage = getErrorMessage(error);
traceFailure(
Action.Tab,
{
databaseName: this.collection.databaseId,
collectionName: this.collection.id(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle,
error: errorMessage,
errorStack: getErrorStack(error),
},
this.props.settingsTab.onLoadStartKey
);
logConsoleError(`Error while fetching container settings for container ${this.collection.id()}: ${errorMessage}`);
} finally {
this.props.settingsTab.isExecuting(false);
}
}
private getMongoIndexesToSave = (): MongoIndex[] => {
let finalIndexes: MongoIndex[] = [];
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
@@ -912,6 +883,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const scaleComponentProps: ScaleComponentProps = {
collection: this.collection,
database: this.database,
container: this.container,
isFixedContainer: this.isFixedContainer,
onThroughputChange: this.onThroughputChange,
throughput: this.state.throughput,
@@ -937,12 +909,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
);
}
if (!this.state.offerLoaded) {
return <></>;
}
const subSettingsComponentProps: SubSettingsComponentProps = {
collection: this.collection,
container: this.container,
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
changeFeedPolicyVisible: this.changeFeedPolicyVisible,
timeToLive: this.state.timeToLive,
@@ -995,6 +964,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
collection: this.collection,
container: this.container,
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange,

View File

@@ -0,0 +1,14 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
export class SettingsComponentAdapter implements ReactAdapter {
public parameters: ko.Computed<boolean>;
constructor(private props: SettingsComponentProps) {}
public renderComponent(): JSX.Element {
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
}
}

View File

@@ -1,12 +1,13 @@
import { shallow } from "enzyme";
import React from "react";
import { ConflictResolutionComponentProps, ConflictResolutionComponent } from "./ConflictResolutionComponent";
import { container, collection } from "../TestUtils";
import * as DataModels from "../../../../Contracts/DataModels";
import { collection } from "../TestUtils";
import { ConflictResolutionComponent, ConflictResolutionComponentProps } from "./ConflictResolutionComponent";
describe("ConflictResolutionComponent", () => {
const baseProps: ConflictResolutionComponentProps = {
collection: collection,
container: container,
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
onConflictResolutionPolicyModeChange: () => {

View File

@@ -1,19 +1,21 @@
import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } from "@fluentui/react";
import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels";
import * as DataModels from "../../../../Contracts/DataModels";
import Explorer from "../../../Explorer";
import {
conflictResolutionCustomToolTip,
conflictResolutionLwwTooltip,
getChoiceGroupStyles,
getTextFieldStyles,
conflictResolutionLwwTooltip,
conflictResolutionCustomToolTip,
subComponentStackProps,
getChoiceGroupStyles,
} from "../SettingsRenderUtils";
import { isDirty } from "../SettingsUtils";
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react";
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
import { isDirty } from "../SettingsUtils";
export interface ConflictResolutionComponentProps {
collection: ViewModels.Collection;
container: Explorer;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;

View File

@@ -1,8 +1,8 @@
import { shallow } from "enzyme";
import React from "react";
import { renderToString } from "react-dom/server";
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
import { renderToString } from "react-dom/server";
describe("MongoIndexingPolicyComponent", () => {
const baseProps: MongoIndexingPolicyComponentProps = {
@@ -84,7 +84,7 @@ describe("MongoIndexingPolicyComponent", () => {
];
test.each(cases)(
"",
"test Mongo Indexing Policy",
(
notification: MongoNotificationMessage,
indexToDropIsPresent: boolean,
@@ -109,12 +109,13 @@ describe("MongoIndexingPolicyComponent", () => {
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
isMongoIndexingPolicyDiscardable
);
if (mongoWarningNotificationMessage) {
const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
expect(elementAsString).toContain(mongoWarningNotificationMessage);
} else {
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
}
const warningNotificationElementAsString = renderToString(
mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()
);
expect(warningNotificationElementAsString.includes(mongoWarningNotificationMessage)).toEqual(
!!mongoWarningNotificationMessage
);
}
);
});

View File

@@ -7,17 +7,20 @@ import * as SharedConstants from "../../../../Shared/Constants";
import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer";
import { throughputUnit } from "../SettingsRenderUtils";
import { collection } from "../TestUtils";
import { collection, container } from "../TestUtils";
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer();
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
const targetThroughput = 6000;
const baseProps: ScaleComponentProps = {
collection: collection,
database: undefined,
container: container,
isFixedContainer: false,
onThroughputChange: () => {
return;
@@ -108,7 +111,7 @@ describe("ScaleComponent", () => {
let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
let newProps = { ...baseProps };
let newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps);
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
@@ -121,7 +124,7 @@ describe("ScaleComponent", () => {
let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
const newProps = { ...baseProps };
const newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
});

View File

@@ -7,7 +7,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
import Explorer from "../../../Explorer";
import {
getTextFieldStyles,
getThroughputApplyLongDelayMessage,
@@ -23,6 +23,7 @@ import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents
export interface ScaleComponentProps {
collection: ViewModels.Collection;
database: ViewModels.Database;
container: Explorer;
isFixedContainer: boolean;
onThroughputChange: (newThroughput: number) => void;
throughput: number;
@@ -108,7 +109,11 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public canThroughputExceedMaximumValue = (): boolean => {
return !this.props.isFixedContainer && configContext.platform === Platform.Portal && !isRunningOnNationalCloud();
return (
!this.props.isFixedContainer &&
configContext.platform === Platform.Portal &&
!this.props.container.isRunningOnNationalCloud()
);
};
public getInitialNotificationElement = (): JSX.Element => {

View File

@@ -4,12 +4,14 @@ import { DatabaseAccount } from "../../../../Contracts/DataModels";
import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer";
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
import { collection } from "../TestUtils";
import { collection, container } from "../TestUtils";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
describe("SubSettingsComponent", () => {
const baseProps: SubSettingsComponentProps = {
collection: collection,
container: container,
timeToLive: TtlType.On,
timeToLiveBaseline: TtlType.On,
onTtlChange: () => {

View File

@@ -2,6 +2,7 @@ import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text,
import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels";
import { userContext } from "../../../../UserContext";
import Explorer from "../../../Explorer";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import {
changeFeedPolicyToolTip,
@@ -27,6 +28,8 @@ import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
export interface SubSettingsComponentProps {
collection: ViewModels.Collection;
container: Explorer;
timeToLive: TtlType;
timeToLiveBaseline: TtlType;

View File

@@ -36,6 +36,7 @@ describe("SettingsUtils", () => {
isDatabaseExpanded: ko.observable(false),
isDatabaseShared: ko.computed(() => true),
selectedSubnodeKind: ko.observable(undefined),
selectDatabase: undefined,
expandDatabase: undefined,
collapseDatabase: undefined,
loadCollections: undefined,

View File

@@ -5,7 +5,7 @@ import Explorer from "../../Explorer";
export const container = new Explorer();
export const collection = ({
export const collection = {
container: container,
databaseId: "test",
id: ko.observable<string>("test"),
@@ -43,4 +43,4 @@ export const collection = ({
readSettings: () => {
return;
},
} as unknown) as ViewModels.Collection;
} as unknown as ViewModels.Collection;

View File

@@ -30,11 +30,24 @@ exports[`SettingsComponent renders 1`] = `
"container": Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
@@ -43,22 +56,46 @@ exports[`SettingsComponent renders 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
@@ -82,6 +119,82 @@ exports[`SettingsComponent renders 1`] = `
"usageSizeInKB": [Function],
}
}
container={
Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
}
}
isAutoPilotSelected={false}
isFixedContainer={false}
onAutoPilotSelected={[Function]}
@@ -116,11 +229,24 @@ exports[`SettingsComponent renders 1`] = `
"container": Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
@@ -129,22 +255,46 @@ exports[`SettingsComponent renders 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
@@ -168,6 +318,82 @@ exports[`SettingsComponent renders 1`] = `
"usageSizeInKB": [Function],
}
}
container={
Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
}
}
geospatialConfigType="Geometry"
geospatialConfigTypeBaseline="Geometry"
isAnalyticalStorageEnabled={false}

View File

@@ -4,7 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
const props = {
isDatabase: false,
showFreeTierExceedThroughputTooltip: true,
isSharded: true,
isSharded: false,
setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(),
onCostAcknowledgeChange: () => jest.fn(),

View File

@@ -41,16 +41,9 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
} else {
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
let maxRU: string;
if (userContext.isTryCosmosDBSubscription) {
maxRU = Constants.TryCosmosExperience.maxRU.toLocaleString();
} else if (!isSharded) {
maxRU = "10000";
} else {
maxRU = "unlimited";
}
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
}
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;

View File

@@ -0,0 +1,194 @@
<div>
<div>
<p class="pkPadding">
<!-- ko if: showAsMandatory -->
<span class="mandatoryStar">*</span>
<!-- /ko -->
<span data-bind="text: label"></span>
<!-- ko if: infoBubbleText -->
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
<span data-bind="text: infoBubbleText" class="tooltiptext throughputRuInfo"></span>
</span>
<!-- /ko -->
</p>
</div>
<!-- ko if: !isFixed -->
<div class="throughputModeContainer">
<input
class="throughputModeRadio"
aria-label="Autopilot mode"
type="radio"
role="radio"
tabindex="0"
data-bind="
checked: isAutoPilotSelected,
checkedValue: true,
attr: {
id: throughputAutoPilotRadioId,
name: throughputModeRadioName,
'aria-checked': isAutoPilotSelected() ? 'true' : 'false'
}"
/>
<span
class="throughputModeSpace"
data-bind="
attr: {
for: throughputAutoPilotRadioId
}"
>Autoscale
</span>
<input
class="throughputModeRadio nonFirstRadio"
aria-label="Manual mode"
type="radio"
role="radio"
tabindex="0"
data-bind="
checked: isAutoPilotSelected,
checkedValue: false,
attr: {
id: throughputProvisionedRadioId,
name: throughputModeRadioName,
'aria-checked': !isAutoPilotSelected() ? 'true' : 'false'
}"
/>
<span
class="throughputModeSpace"
data-bind="attr: {
for: throughputProvisionedRadioId
}"
>Manual
</span>
</div>
<!-- /ko -->
<div data-bind="visible: isAutoPilotSelected">
<p>
<span
>Provision maximum RU/s required by this resource. Estimate your required RU/s with
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a>.</span
>
</p>
<p>
<span>Max RU/s</span>
</p>
<div data-bind="setTemplateReady: true">
<input
data-bind="textInput: overrideWithProvisionedThroughputSettings() ? '' : maxAutoPilotThroughputSet, attr:{
disabled: overrideWithProvisionedThroughputSettings(),
step: step,
'class':'migration collid select-font-size',
min: minAutoPilotThroughput,
'aria-label': 'Max request units per second',
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
css: {
dirty: maxAutoPilotThroughputSet.editableIsDirty
}
}"
/>
</div>
<p data-bind="visible: overrideWithProvisionedThroughputSettings && !overrideWithProvisionedThroughputSettings()">
<span
data-bind="
html: autoPilotUsageCost"
></span>
</p>
<p
data-bind="visible: costsVisible && overrideWithProvisionedThroughputSettings && !overrideWithProvisionedThroughputSettings()"
>
<span data-bind="html: requestUnitsUsageCost"></span>
</p>
<!-- ko if: spendAckVisible -->
<p class="pkPadding">
<input
type="checkbox"
aria-label="acknowledge spend throughput"
data-bind="
attr: {
title: spendAckText,
id: spendAckId
},
checked: spendAckChecked"
/>
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
</p>
<!-- /ko -->
<!-- ko if: isFixed -->
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
<!-- /ko -->
</div>
<div data-bind="visible: !isAutoPilotSelected()">
<p>
<span
>Estimate your required throughput with
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
>
</p>
<div class="inputTooltip">
<span
data-bind="text: freeTierExceedThroughputTooltip, visible: showFreeTierExceedThroughputTooltip"
class="inputTooltipText"
></span>
</div>
<div data-bind="setTemplateReady: true">
<input
data-bind="
textInput: overrideWithAutoPilotSettings() ? maxAutoPilotThroughputSet : value,
css: {
dirty: value.editableIsDirty
},
enable: isEnabled,
attr:{
type: isManualThroughputInputFieldRequired() ? 'number' : 'hidden',
'data-test': testId,
'class': cssClass,
step: step,
min: minimum,
max: canExceedMaximumValue() ? null : maximum,
'aria-label': ariaLabel,
disabled: overrideWithAutoPilotSettings(),
required: isManualThroughputInputFieldRequired()
}"
/>
</div>
<div class="freeTierInlineWarning" data-bind="visible: showFreeTierExceedThroughputWarning">
<span class="freeTierWarningIcon"><img src="/images/warning.svg" alt="Warning" /></span>
<span class="freeTierWarningMessage" data-bind="text: freeTierExceedThroughputWarning"></span>
</div>
<p data-bind="visible: costsVisible">
<span data-bind="html: requestUnitsUsageCost"></span>
</p>
<!-- ko if: spendAckVisible -->
<p class="pkPadding">
<input
type="checkbox"
aria-label="acknowledge spend throughput"
data-bind="
attr: {
title: spendAckText,
id: spendAckId
},
checked: spendAckChecked"
/>
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
</p>
<!-- /ko -->
<!-- ko if: isFixed -->
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
<!-- /ko -->
</div>
</div>

View File

@@ -3,7 +3,7 @@
exports[`ThroughputInput Pane should render Default properly 1`] = `
<ThroughputInput
isDatabase={false}
isSharded={true}
isSharded={false}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setThroughputValue={[Function]}

View File

@@ -12,11 +12,11 @@ import {
IContextualMenuItemProps,
IContextualMenuProps,
} from "@fluentui/react";
import LoadingIndicator_3Squares from "images/LoadingIndicator_3Squares.gif";
import TriangleDownIcon from "images/Triangle-down.svg";
import TriangleRightIcon from "images/Triangle-right.svg";
import * as React from "react";
import AnimateHeight from "react-animate-height";
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import * as Constants from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -58,7 +58,7 @@ export interface TreeComponentProps {
export class TreeComponent extends React.Component<TreeComponentProps> {
public render(): JSX.Element {
return (
<div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree">
<div style={this.props.style} className={`treeComponent ${this.props.className}`}>
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
</div>
);
@@ -172,7 +172,6 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
role="treeitem"
>
<div
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}

View File

@@ -3,7 +3,6 @@
exports[`TreeComponent renders a simple tree 1`] = `
<div
className="treeComponent tree"
role="tree"
>
<TreeNodeComponent
generation={0}
@@ -38,7 +37,6 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
className=" main2 nodeItem "
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
>
<div
className="treeNodeHeader "
@@ -139,7 +137,6 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
className="nodeClassname main12 nodeItem "
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
>
<div
className="treeNodeHeader "
@@ -288,7 +285,6 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
className=" main2 nodeItem "
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
>
<div
className="treeNodeHeader "
@@ -360,7 +356,6 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
className="nodeClassname main12 nodeItem "
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
>
<div
className="treeNodeHeader "
@@ -528,7 +523,6 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
className=" main2 nodeItem "
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
>
<div
className="treeNodeHeader "

View File

@@ -2,22 +2,22 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
jest.mock("../../Common/dataAccess/createCollection");
jest.mock("../../Common/dataAccess/createDocument");
import * as ko from "knockout";
import Q from "q";
import { createDocument } from "../../Common/dataAccess/createDocument";
import { DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { updateUserContext } from "../../UserContext";
import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
describe("ContainerSampleGenerator", () => {
let explorerStub: Explorer;
beforeAll(() => {
explorerStub = {
refreshAllDatabases: () => {},
} as Explorer;
});
const createExplorerStub = (database: ViewModels.Database): Explorer => {
const explorerStub = {} as Explorer;
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
explorerStub.findDatabaseWithId = () => database;
explorerStub.refreshAllDatabases = () => Q.resolve();
return explorerStub;
};
beforeEach(() => {
(createDocument as jest.Mock).mockResolvedValue(undefined);
@@ -59,7 +59,8 @@ describe("ContainerSampleGenerator", () => {
loadCollections: () => {},
} as ViewModels.Database;
database.findCollectionWithId = () => collection;
useDatabases.getState().addDatabases([database]);
const explorerStub = createExplorerStub(database);
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
generator.setData(sampleData);
@@ -107,8 +108,8 @@ describe("ContainerSampleGenerator", () => {
} as ViewModels.Database;
database.findCollectionWithId = () => collection;
collection.databaseId = database.id();
useDatabases.getState().addDatabases([database]);
const explorerStub = createExplorerStub(database);
updateUserContext({
databaseAccount: {
properties: {
@@ -125,6 +126,7 @@ describe("ContainerSampleGenerator", () => {
it("should not create any sample for Mongo API account", async () => {
const experience = "Sample generation not supported for this API Mongo";
const explorerStub = createExplorerStub(undefined);
updateUserContext({
databaseAccount: {
properties: {
@@ -139,6 +141,7 @@ describe("ContainerSampleGenerator", () => {
it("should not create any sample for Table API account", async () => {
const experience = "Sample generation not supported for this API Tables";
const explorerStub = createExplorerStub(undefined);
updateUserContext({
databaseAccount: {
properties: {
@@ -160,6 +163,7 @@ describe("ContainerSampleGenerator", () => {
},
} as DatabaseAccount,
});
const explorerStub = createExplorerStub(undefined);
// Rejects with error that contains experience
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
});

View File

@@ -7,7 +7,6 @@ import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"
import GraphTab from ".././Tabs/GraphTab";
import Explorer from "../Explorer";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import { useDatabases } from "../useDatabases";
interface SampleDataFile extends DataModels.CreateCollectionParams {
data: any[];
@@ -60,7 +59,7 @@ export class ContainerSampleGenerator {
await createCollection(createRequest);
await this.container.refreshAllDatabases();
const database = useDatabases.getState().findDatabaseWithId(this.sampleDataFile.databaseId);
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
if (!database) {
return undefined;
}

View File

@@ -2,7 +2,6 @@ import * as ko from "knockout";
import * as sinon from "sinon";
import { Collection, Database } from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { DataSamplesUtil } from "./DataSamplesUtil";
@@ -17,8 +16,8 @@ describe("DataSampleUtils", () => {
collections: ko.observableArray<Collection>([collection]),
} as Database;
const explorer = {} as Explorer;
explorer.databases = ko.observableArray<Database>([database]);
explorer.showOkModalDialog = () => {};
useDatabases.getState().addDatabases([database]);
const dataSamplesUtil = new DataSamplesUtil(explorer);
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);

View File

@@ -2,7 +2,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext";
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
export class DataSamplesUtil {
@@ -18,7 +17,7 @@ export class DataSamplesUtil {
const databaseName = generator.getDatabaseId();
const containerName = generator.getCollectionId();
if (this.hasContainer(databaseName, containerName, useDatabases.getState().databases)) {
if (this.hasContainer(databaseName, containerName, this.container.databases())) {
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
logConsoleError(msg);

View File

@@ -0,0 +1,43 @@
jest.mock("./../Common/dataAccess/deleteDatabase");
jest.mock("./../Shared/Telemetry/TelemetryProcessor");
import * as ko from "knockout";
import { deleteDatabase } from "./../Common/dataAccess/deleteDatabase";
import * as ViewModels from "./../Contracts/ViewModels";
import Explorer from "./Explorer";
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
let explorer: Explorer;
beforeAll(() => {
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
});
beforeEach(() => {
explorer = new Explorer();
});
it("should be true if only 1 database", () => {
const database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastDatabase()).toBe(true);
});
it("should be false if only 2 databases", () => {
const database = {} as ViewModels.Database;
const database2 = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
expect(explorer.isLastDatabase()).toBe(false);
});
it("should be false if not last empty database", () => {
const database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastNonEmptyDatabase()).toBe(false);
});
it("should be true if last non empty database", () => {
const database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastNonEmptyDatabase()).toBe(true);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,13 @@
* Editor for neighbors (targets or sources)
*/
import AddPropertyIcon from "images/Add-property.svg";
import DeleteIcon from "images/delete.svg";
import * as React from "react";
import { NeighborVertexBasicInfo, EditedEdges, GraphNewEdgeData, PossibleVertex } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import DeleteIcon from "../../../../images/delete.svg";
import AddPropertyIcon from "../../../../images/Add-property.svg";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import { EditedEdges, GraphNewEdgeData, NeighborVertexBasicInfo, PossibleVertex } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil";
export interface EditorNeighborsComponentProps {
isSource: boolean;

View File

@@ -3,13 +3,13 @@
* Read-only properties
*/
import AddIcon from "images/Add-property.svg";
import DeleteIcon from "images/delete.svg";
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { EditedProperties } from "./GraphExplorer";
import DeleteIcon from "../../../../images/delete.svg";
import AddIcon from "../../../../images/Add-property.svg";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { EditedProperties } from "./GraphExplorer";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
export interface EditorNodePropertiesComponentProps {
editedProperties: EditedProperties;

View File

@@ -10,7 +10,7 @@ import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPag
import * as DataModels from "../../../Contracts/DataModels";
import * as StorageUtility from "../../../Shared/StorageUtility";
import { TabComponent } from "../../Controls/Tabs/TabComponent";
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import GraphTab from "../../Tabs/GraphTab";
import * as D3ForceGraph from "./D3ForceGraph";
import { GraphData } from "./GraphData";
@@ -207,11 +207,9 @@ describe("GraphExplorer", () => {
const gVRU = 123.456;
const disableMonacoEditor = (graphExplorer: GraphExplorer) => {
renderResultAsJsonStub = sinon.stub(graphExplorer, "renderResultAsJson").callsFake(
(): JSX.Element => {
return <div>[Monaco Editor Stub]</div>;
}
);
renderResultAsJsonStub = sinon.stub(graphExplorer, "renderResultAsJson").callsFake((): JSX.Element => {
return <div>[Monaco Editor Stub]</div>;
});
};
interface AjaxResponse {

View File

@@ -1,8 +1,8 @@
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import LoadGraphIcon from "images/LoadGraph.png";
import LoadingIndicatorIcon from "images/LoadingIndicator_3Squares.gif";
import * as Q from "q";
import * as React from "react";
import LoadGraphIcon from "../../../../images/LoadGraph.png";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import * as Constants from "../../../Common/Constants";
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
@@ -18,7 +18,7 @@ import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Ut
import { EditorReact } from "../../Controls/Editor/EditorReact";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import * as TabComponent from "../../Controls/Tabs/TabComponent";
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { IGraphConfig } from "../../Tabs/GraphTab";
import { ArraysByKeyCache } from "./ArraysByKeyCache";
import * as D3ForceGraph from "./D3ForceGraph";
@@ -633,40 +633,38 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
promise = Q.resolve(0);
}
promise = promise.then(
(outEPairsNb: number): Q.Promise<number> => {
const inEdgesToFetch = totalEdgesToFetch - outEPairsNb;
if (!vertex._inEAllLoaded && inEdgesToFetch > 0) {
let start: number;
if (offsetIndex <= vertex._outEdgeIds.length) {
start = 0;
} else {
start = offsetIndex - vertex._outEdgeIds.length;
}
return this.fetchEdgeVertexPairs(false, vertex, start, inEdgesToFetch).then(
(pairs: EdgeVertexPair[]): number => {
vertex._inEAllLoaded = pairs.length < inEdgesToFetch;
const pairsToAdd = pairs.slice(0, GraphExplorer.LOAD_PAGE_SIZE - outEPairsNb);
pairsToAdd.forEach((p: EdgeVertexPair) => {
GraphData.GraphData.addInE(vertex, p.e.label, p.e);
GraphUtil.addRootChildToGraph(vertex, p.v, graphData);
graphData.addEdge(p.e);
vertex._inEdgeIds.push(p.e.id);
// Cache results (graphdata now contains a vertex with outE's filled in)
this.edgeInfoCache.addVertex(graphData.getVertexById(p.v.id));
});
addedEdgesNb += pairsToAdd.length;
return outEPairsNb + pairs.length;
}
);
promise = promise.then((outEPairsNb: number): Q.Promise<number> => {
const inEdgesToFetch = totalEdgesToFetch - outEPairsNb;
if (!vertex._inEAllLoaded && inEdgesToFetch > 0) {
let start: number;
if (offsetIndex <= vertex._outEdgeIds.length) {
start = 0;
} else {
return Q.resolve(outEPairsNb);
start = offsetIndex - vertex._outEdgeIds.length;
}
return this.fetchEdgeVertexPairs(false, vertex, start, inEdgesToFetch).then(
(pairs: EdgeVertexPair[]): number => {
vertex._inEAllLoaded = pairs.length < inEdgesToFetch;
const pairsToAdd = pairs.slice(0, GraphExplorer.LOAD_PAGE_SIZE - outEPairsNb);
pairsToAdd.forEach((p: EdgeVertexPair) => {
GraphData.GraphData.addInE(vertex, p.e.label, p.e);
GraphUtil.addRootChildToGraph(vertex, p.v, graphData);
graphData.addEdge(p.e);
vertex._inEdgeIds.push(p.e.id);
// Cache results (graphdata now contains a vertex with outE's filled in)
this.edgeInfoCache.addVertex(graphData.getVertexById(p.v.id));
});
addedEdgesNb += pairsToAdd.length;
return outEPairsNb + pairs.length;
}
);
} else {
return Q.resolve(outEPairsNb);
}
);
});
return promise.then((nbPairsFetched: number) => {
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
@@ -1221,16 +1219,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private getPossibleRootNodes(): LeftPane.CaptionId[] {
const key = this.state.igraphConfig.nodeCaption;
return $.map(
this.state.rootMap,
(value: any, index: number): LeftPane.CaptionId => {
let result = GraphData.GraphData.getNodePropValue(value, key);
return {
caption: result !== undefined ? result : value.id,
id: value.id,
};
}
);
return $.map(this.state.rootMap, (value: any, index: number): LeftPane.CaptionId => {
let result = GraphData.GraphData.getNodePropValue(value, key);
return {
caption: result !== undefined ? result : value.id,
id: value.id,
};
});
}
/**

View File

@@ -0,0 +1,74 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../../Contracts/ViewModels";
import { IGraphConfig } from "../../Tabs/GraphTab";
import { GraphAccessor, GraphExplorer } from "./GraphExplorer";
interface Parameter {
onIsNewVertexDisabledChange: (isEnabled: boolean) => void;
onGraphAccessorCreated: (instance: GraphAccessor) => void;
onIsFilterQueryLoading: (isFilterQueryLoading: boolean) => void;
onIsValidQuery: (isValidQuery: boolean) => void;
onIsPropertyEditing: (isEditing: boolean) => void;
onIsGraphDisplayed: (isDisplayed: boolean) => void;
onResetDefaultGraphConfigValues: () => void;
collectionPartitionKeyProperty: string;
graphBackendEndpoint: string;
databaseId: string;
collectionId: string;
masterKey: string;
onLoadStartKey: number;
onLoadStartKeyChange: (newKey: number) => void;
resourceId: string;
igraphConfigUiData: ViewModels.IGraphConfigUiData;
igraphConfig: IGraphConfig;
setIConfigUiData?: (data: string[]) => void;
}
interface IGraphExplorerProps {
isChanged: boolean;
}
interface IGraphExplorerStates {
isChangedState: boolean;
}
export interface GraphExplorerAdapter
extends ReactAdapter,
React.Component<IGraphExplorerProps, IGraphExplorerStates> {}
export class GraphExplorerAdapter implements ReactAdapter {
public params: Parameter;
public parameters = {};
public isNewVertexDisabled: boolean;
public constructor(params: Parameter, props?: IGraphExplorerProps) {
this.params = params;
}
public renderComponent(): JSX.Element {
return (
<GraphExplorer
onIsNewVertexDisabledChange={this.params.onIsNewVertexDisabledChange}
onGraphAccessorCreated={this.params.onGraphAccessorCreated}
onIsFilterQueryLoadingChange={this.params.onIsFilterQueryLoading}
onIsValidQueryChange={this.params.onIsValidQuery}
onIsPropertyEditing={this.params.onIsPropertyEditing}
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
graphBackendEndpoint={this.params.graphBackendEndpoint}
databaseId={this.params.databaseId}
collectionId={this.params.collectionId}
masterKey={this.params.masterKey}
onLoadStartKey={this.params.onLoadStartKey}
onLoadStartKeyChange={this.params.onLoadStartKeyChange}
resourceId={this.params.resourceId}
igraphConfigUiData={this.params.igraphConfigUiData}
igraphConfig={this.params.igraphConfig}
setIConfigUiData={this.params.setIConfigUiData}
/>
);
}
}

View File

@@ -1,7 +1,9 @@
import * as sinon from "sinon";
import { GraphData, GremlinEdge, GremlinVertex } from "./GraphData";
import { GraphExplorer } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil";
import { GraphData, GremlinVertex, GremlinEdge } from "./GraphData";
import * as sinon from "sinon";
import { GraphExplorer } from "./GraphExplorer";
window.$ = window.jQuery = require("jquery");
const OUT_E_MATCHER = "g\\.V\\(.*\\).outE\\(\\).*\\.as\\('e'\\).inV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
const IN_E_MATCHER = "g\\.V\\(.*\\).inE\\(\\).*\\.as\\('e'\\).outV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";

View File

@@ -94,236 +94,16 @@ describe("Gremlin Simple Client", () => {
result: { data: ["é"], meta: {} },
};
const expectedDecodedUint8ArrayValues = [
123,
34,
114,
101,
113,
117,
101,
115,
116,
73,
100,
34,
58,
34,
100,
55,
55,
50,
102,
56,
57,
55,
45,
48,
100,
52,
100,
45,
52,
99,
100,
49,
45,
98,
51,
54,
48,
45,
100,
100,
102,
54,
99,
56,
54,
98,
57,
51,
97,
51,
34,
44,
34,
115,
116,
97,
116,
117,
115,
34,
58,
123,
34,
99,
111,
100,
101,
34,
58,
50,
48,
48,
44,
34,
97,
116,
116,
114,
105,
98,
117,
116,
101,
115,
34,
58,
123,
34,
103,
114,
97,
112,
104,
69,
120,
101,
99,
117,
116,
105,
111,
110,
83,
116,
97,
116,
117,
115,
34,
58,
50,
48,
48,
44,
34,
83,
116,
111,
114,
97,
103,
101,
82,
85,
34,
58,
50,
46,
50,
57,
44,
34,
67,
111,
109,
112,
117,
116,
101,
82,
85,
34,
58,
49,
46,
48,
55,
44,
34,
80,
101,
114,
80,
97,
114,
116,
105,
116,
105,
111,
110,
67,
111,
109,
112,
117,
116,
101,
67,
104,
97,
114,
103,
101,
115,
34,
58,
123,
125,
125,
44,
34,
109,
101,
115,
115,
97,
103,
101,
34,
58,
34,
34,
125,
44,
34,
114,
101,
115,
117,
108,
116,
34,
58,
123,
34,
100,
97,
116,
97,
34,
58,
91,
34,
195,
169,
34,
93,
44,
34,
109,
101,
116,
97,
34,
58,
123,
125,
125,
125,
123, 34, 114, 101, 113, 117, 101, 115, 116, 73, 100, 34, 58, 34, 100, 55, 55, 50, 102, 56, 57, 55, 45, 48, 100,
52, 100, 45, 52, 99, 100, 49, 45, 98, 51, 54, 48, 45, 100, 100, 102, 54, 99, 56, 54, 98, 57, 51, 97, 51, 34, 44,
34, 115, 116, 97, 116, 117, 115, 34, 58, 123, 34, 99, 111, 100, 101, 34, 58, 50, 48, 48, 44, 34, 97, 116, 116,
114, 105, 98, 117, 116, 101, 115, 34, 58, 123, 34, 103, 114, 97, 112, 104, 69, 120, 101, 99, 117, 116, 105, 111,
110, 83, 116, 97, 116, 117, 115, 34, 58, 50, 48, 48, 44, 34, 83, 116, 111, 114, 97, 103, 101, 82, 85, 34, 58, 50,
46, 50, 57, 44, 34, 67, 111, 109, 112, 117, 116, 101, 82, 85, 34, 58, 49, 46, 48, 55, 44, 34, 80, 101, 114, 80,
97, 114, 116, 105, 116, 105, 111, 110, 67, 111, 109, 112, 117, 116, 101, 67, 104, 97, 114, 103, 101, 115, 34, 58,
123, 125, 125, 44, 34, 109, 101, 115, 115, 97, 103, 101, 34, 58, 34, 34, 125, 44, 34, 114, 101, 115, 117, 108,
116, 34, 58, 123, 34, 100, 97, 116, 97, 34, 58, 91, 34, 195, 169, 34, 93, 44, 34, 109, 101, 116, 97, 34, 58, 123,
125, 125, 125,
];
// We do our best here to emulate what the server should return
const gremlinResponseData = new Uint8Array(<any>expectedDecodedUint8ArrayValues).buffer;

View File

@@ -1,8 +1,8 @@
import CollapseArrowIcon from "images/Collapse_arrow_14x14.svg";
import ExpandIcon from "images/Expand_14x14.svg";
import LoadingIndicatorIcon from "images/LoadingIndicator_3Squares.gif";
import * as React from "react";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
import ExpandIcon from "../../../../images/Expand_14x14.svg";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
interface MiddlePaneComponentProps {
isTabsContentExpanded: boolean;

View File

@@ -4,16 +4,16 @@
* The mode is controlled by the parent of this component
*/
import CancelIcon from "images/cancel.svg";
import CheckIcon from "images/check1.svg";
import DeleteIcon from "images/delete.svg";
import EditIcon from "images/Edit_entity.svg";
import * as React from "react";
import CancelIcon from "../../../../images/cancel.svg";
import CheckIcon from "../../../../images/check.svg";
import DeleteIcon from "../../../../images/delete.svg";
import EditIcon from "../../../../images/edit.svg";
import * as ViewModels from "../../../Contracts/ViewModels";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";
import { Item } from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import * as EditorNeighbors from "./EditorNeighborsComponent";
import { EditorNodePropertiesComponent } from "./EditorNodePropertiesComponent";
import {

View File

@@ -1,6 +1,6 @@
import CloseIcon from "images/close-black.svg";
import * as React from "react";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import CloseIcon from "../../../../images/close-black.svg";
export interface QueryContainerComponentProps {
initialQuery: string;

View File

@@ -43,7 +43,7 @@ describe("Graph Style Component", () => {
expect(asFragment).toMatchSnapshot();
});
it("should render node properties dropdown list ", () => {
it("should render node properties dropdown list", () => {
const dropDownList = screen.getByText("Show vertex (node) as");
expect(dropDownList).toBeDefined();
});

View File

@@ -1,7 +1,7 @@
import { Dropdown, IDropdownOption, Stack, TextField } from "@fluentui/react";
import AddIcon from "images/Add-property.svg";
import DeleteIcon from "images/delete.svg";
import React, { FunctionComponent, useRef, useState } from "react";
import AddIcon from "../../../../images/Add-property.svg";
import DeleteIcon from "../../../../images/delete.svg";
import { NormalizedEventKey } from "../../../Common/Constants";
import { GremlinPropertyValueType, InputPropertyValueTypeString, NewVertexData } from "../../../Contracts/ViewModels";
import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent";

View File

@@ -3,71 +3,100 @@
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import { CommandBar, ICommandBarItemProps } from "@fluentui/react";
import * as ko from "knockout";
import * as React from "react";
import create, { UseStore } from "zustand";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { StyleConstants } from "../../../Common/Constants";
import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil";
interface Props {
container: Explorer;
export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: Explorer;
private tabsButtons: CommandButtonComponentProps[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: Explorer) {
this.container = container;
this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(
() => container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2
);
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
container.isDatabaseNodeOrNoneSelected,
container.isDatabaseNodeSelected,
container.isNoneSelected,
container.isResourceTokenCollectionNodeSelected,
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
userContext?.databaseAccount,
this.isNotebookTabActive,
container.isServerlessEnabled,
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now());
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.tabsButtons = buttons;
this.triggerRender();
}
public renderComponent(): JSX.Element {
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
const contextButtons = (this.tabsButtons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (this.tabsButtons && this.tabsButtons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (this.isNotebookTabActive()) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);
}
return (
<React.Fragment>
<div className="commandBarContainer">
<CommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
</React.Fragment>
);
}
private triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
}));
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const selectedNodeState = useSelectedNode();
const buttons = useCommandBar((state) => state.contextButtons);
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (buttons && buttons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker", container.memoryUsageInfo));
}
return (
<div className="commandBarContainer">
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
};

View File

@@ -1,22 +1,17 @@
import * as ko from "knockout";
import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import NotebookManager from "../../Notebook/NotebookManager";
import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer;
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
describe("Enable Azure Synapse Link Button", () => {
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
@@ -28,12 +23,17 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount,
});
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = () => false;
});
it("Account is not serverless - button should be visible", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
@@ -41,14 +41,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
it("Account is serverless - button should be hidden", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableServerless" }],
},
} as DatabaseAccount,
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
@@ -58,12 +53,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({
portalEnv: "prod",
databaseAccount: {
properties: {
capabilities: [{ name: "EnableTable" }],
@@ -71,19 +64,18 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount,
});
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
});
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
afterEach(() => {
updateUserContext({
portalEnv: "prod",
});
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
});
it("Notebooks is already enabled - button should be hidden", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined();
});
@@ -91,11 +83,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Account is running on one of the national clouds - button should be hidden", () => {
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
updateUserContext({
portalEnv: "mooncake",
});
mockExplorer.isRunningOnNationalCloud = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined();
});
@@ -103,8 +93,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined();
expect(enableNotebookBtn.disabled).toBe(false);
@@ -114,8 +105,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined();
expect(enableNotebookBtn.disabled).toBe(true);
@@ -127,7 +119,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Open Mongo Shell button", () => {
const openMongoShellBtnLabel = "Open Mongo Shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
@@ -139,6 +130,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount,
});
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
mockExplorer.isShellEnabled = ko.observable(true);
});
@@ -154,7 +148,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.isShellEnabled = ko.observable(true);
});
@@ -162,23 +156,21 @@ describe("CommandBarComponentButtonFactory tests", () => {
updateUserContext({
apiType: "SQL",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
it("Running on a national cloud - button should be hidden", () => {
updateUserContext({
portalEnv: "mooncake",
});
mockExplorer.isRunningOnNationalCloud = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
@@ -186,7 +178,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is not enabled and is available - button should be hidden", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
@@ -194,7 +186,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false);
@@ -205,7 +197,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false);
@@ -217,7 +209,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
mockExplorer.isShellEnabled = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
@@ -225,7 +217,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Open Cassandra Shell button", () => {
const openCassandraShellBtnLabel = "Open Cassandra Shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
@@ -237,6 +228,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount,
});
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
});
beforeEach(() => {
@@ -249,6 +243,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
});
it("Cassandra Api not available - button should be hidden", () => {
@@ -260,23 +255,21 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount,
});
console.log(mockExplorer);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined();
});
it("Running on a national cloud - button should be hidden", () => {
updateUserContext({
portalEnv: "mooncake",
});
mockExplorer.isRunningOnNationalCloud = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined();
});
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined();
});
@@ -284,7 +277,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined();
});
@@ -292,7 +285,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false);
@@ -303,7 +296,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false);
@@ -314,7 +307,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("GitHub buttons", () => {
const connectToGitHubBtnLabel = "Connect to GitHub";
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
@@ -327,10 +319,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
});
beforeEach(() => {
@@ -344,7 +338,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeDefined();
});
@@ -353,7 +347,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const manageGitHubSettingsBtn = buttons.find(
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel
);
@@ -361,7 +355,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeUndefined();
@@ -374,13 +368,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
describe("Resource token", () => {
const mockCollection = { id: ko.observable("test") } as CollectionBase;
useSelectedNode.getState().setSelectedNode(mockCollection);
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
updateUserContext({
authType: AuthType.ResourceToken,
});
@@ -392,7 +384,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
kind: "DocumentDB",
} as DatabaseAccount,
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
expect(buttons.length).toBe(2);
expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
expect(buttons[0].disabled).toBe(false);

View File

@@ -1,46 +1,37 @@
import AddCollectionIcon from "images/AddCollection.svg";
import AddDatabaseIcon from "images/AddDatabase.svg";
import AddSqlQueryIcon from "images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "images/AddStoredProcedure.svg";
import AddTriggerIcon from "images/AddTrigger.svg";
import AddUdfIcon from "images/AddUdf.svg";
import BrowseQueriesIcon from "images/BrowseQuery.svg";
import CosmosTerminalIcon from "images/Cosmos-Terminal.svg";
import FeedbackIcon from "images/Feedback-Command.svg";
import GitHubIcon from "images/github.svg";
import HostedTerminalIcon from "images/Hosted-Terminal.svg";
import EnableNotebooksIcon from "images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "images/notebook/Notebook-reset-workspace.svg";
import OpenInTabIcon from "images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "images/OpenQueryFromDisk.svg";
import SettingsIcon from "images/settings_15x15.svg";
import SynapseIcon from "images/synapse-link.svg";
import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import GitHubIcon from "../../../../images/github.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { OpenFullScreen } from "../../OpenFullScreen";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { SelectedNodeState } from "../../useSelectedNode";
let counter = 0;
export function createStaticCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
return createStaticCommandBarButtonsForResourceToken(container);
}
const newCollectionBtn = createNewCollectionGroup(container);
@@ -74,36 +65,35 @@ export function createStaticCommandBarButtons(
buttons.push(createOpenTerminalButton(container));
buttons.push(createNotebookWorkspaceResetButton(container));
buttons.push(createDivider());
buttons.push(createOpenPostgreSQLTerminalButton(container));
if (userContext.apiType === "Mongo" &&
container.isShellEnabled() &&
selectedNodeState.isDatabaseNodeOrNoneSelected()){
if (
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) ||
userContext.apiType === "Cassandra"
) {
buttons.push(createDivider());
if (userContext.apiType === "Cassandra") {
buttons.push(createOpenCassandraTerminalButton(container));
} else {
buttons.push(createOpenMongoTerminalButton(container));
}
if (userContext.apiType === "Cassandra") {
buttons.push(createOpenCassandraTerminalButton(container));
}
}
} else {
if (!isRunningOnNationalCloud()) {
if (!container.isRunningOnNationalCloud()) {
buttons.push(createEnableNotebooksButton(container));
}
}
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
if (!container.isDatabaseNodeOrNoneSelected()) {
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
if (isQuerySupported) {
buttons.push(createDivider());
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
const newSqlQueryBtn = createNewSQLQueryButton(container);
buttons.push(newSqlQueryBtn);
}
if (isQuerySupported && selectedNodeState.findSelectedCollection()) {
if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) {
const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
buttons.push(openQueryBtn);
}
@@ -113,16 +103,16 @@ export function createStaticCommandBarButtons(
iconSrc: AddStoredProcedureIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
disabled: container.isDatabaseNodeOrNoneSelected(),
};
newStoredProcedureBtn.children = createScriptCommandButtons(selectedNodeState);
newStoredProcedureBtn.children = createScriptCommandButtons(container);
buttons.push(newStoredProcedureBtn);
}
}
@@ -130,19 +120,16 @@ export function createStaticCommandBarButtons(
return buttons;
}
export function createContextCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
if (container.isShellEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
@@ -152,7 +139,7 @@ export function createContextCommandBarButtons(
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
disabled: container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
};
buttons.push(newMongoShellBtn);
}
@@ -165,7 +152,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
{
iconSrc: SettingsIcon,
iconAlt: "Settings",
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane />),
onCommandClick: () => container.openSettingPane(),
commandButtonLabel: undefined,
ariaLabel: "Settings",
tooltipText: "Settings",
@@ -174,22 +161,19 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
},
];
const showOpenFullScreen =
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
if (showOpenFullScreen) {
if (container.isHostedDataExplorerEnabled()) {
const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = {
iconSrc: OpenInTabIcon,
iconAlt: label,
onCommandClick: () => {
useSidePanel.getState().openSidePanel("Open Full Screen", <OpenFullScreen />);
container.openSidePanel("Open Full Screen", <OpenFullScreen />);
},
commandButtonLabel: undefined,
ariaLabel: label,
tooltipText: label,
hasPopup: false,
disabled: !showOpenFullScreen,
disabled: !container.isHostedDataExplorerEnabled(),
className: "OpenFullScreen",
};
buttons.push(fullScreenButton);
@@ -248,7 +232,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
return undefined;
}
if (isServerlessAccount()) {
if (container.isServerlessEnabled()) {
return undefined;
}
@@ -287,20 +271,20 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
};
}
function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandButtonComponentProps {
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps {
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
const label = "New SQL Query";
return {
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
disabled: container.isDatabaseNodeOrNoneSelected(),
};
} else if (userContext.apiType === "Mongo") {
const label = "New Query";
@@ -308,24 +292,23 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
disabled: container.isDatabaseNodeOrNoneSelected(),
};
}
return undefined;
}
export function createScriptCommandButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] {
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
const shouldEnableScriptsCommands: boolean =
!selectedNodeState.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
if (shouldEnableScriptsCommands) {
const label = "New Stored Procedure";
@@ -333,13 +316,13 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
iconSrc: AddStoredProcedureIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newStoredProcedureBtn);
}
@@ -350,13 +333,13 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
iconSrc: AddUdfIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newUserDefinedFunctionBtn);
}
@@ -367,13 +350,13 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
iconSrc: AddTriggerIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newTriggerBtn);
}
@@ -420,12 +403,12 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
};
}
function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
function createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query From Disk";
return {
iconSrc: OpenQueryFromDiskIcon,
iconAlt: label,
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
onCommandClick: () => container.openLoadQueryPanel(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
@@ -467,19 +450,6 @@ function createOpenTerminalButton(container: Explorer): CommandButtonComponentPr
};
}
function createOpenPostgreSQLTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open PostgreSQL Shell";
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.PostgreSQL),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
function createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Mongo Shell";
const tooltip =
@@ -559,25 +529,19 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
};
}
function createStaticCommandBarButtonsForResourceToken(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(container);
const openQueryBtn = createOpenQueryButton(container);
const isResourceTokenCollectionNodeSelected: boolean =
container.resourceTokenCollection() &&
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
newSqlQueryBtn.onCommandClick = () => {
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
};
openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
}
return [newSqlQueryBtn, openQueryBtn];

View File

@@ -1,15 +1,8 @@
import {
Dropdown,
ICommandBarItemProps,
IComponentAsProps,
IconType,
IDropdownOption,
IDropdownStyles,
} from "@fluentui/react";
import { ICommandBarItemProps, IconType, IDropdownOption, IDropdownStyles } from "@fluentui/react";
import ChevronDownIcon from "images/Chevron_down.svg";
import { Observable } from "knockout";
import * as React from "react";
import _ from "underscore";
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
import { StyleConstants } from "../../../Common/Constants";
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
@@ -26,150 +19,135 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
return btns
.filter((btn) => btn)
.map(
(btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => {
if (btn.isDivider) {
return createDivider(btn.commandButtonLabel);
}
.map((btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => {
if (btn.isDivider) {
return createDivider(btn.commandButtonLabel);
}
const isSplit = !!btn.children && btn.children.length > 0;
const label = btn.commandButtonLabel || btn.tooltipText;
const result: ICommandBarItemProps = {
iconProps: {
style: {
width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: btn.iconName ? "baseline" : undefined,
},
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName,
const isSplit = !!btn.children && btn.children.length > 0;
const label = btn.commandButtonLabel || btn.tooltipText;
const result: ICommandBarItemProps = {
iconProps: {
style: {
width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: btn.iconName ? "baseline" : undefined,
},
onClick: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
btn.onCommandClick(ev);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName,
},
onClick: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
btn.onCommandClick(ev);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
},
key: `${btn.commandButtonLabel}${index}`,
text: label,
"data-test": label,
title: btn.tooltipText,
name: label,
disabled: btn.disabled,
ariaLabel: btn.ariaLabel,
buttonStyles: {
root: {
backgroundColor: backgroundColor,
height: buttonHeightPx,
paddingRight: 0,
paddingLeft: 0,
minWidth: 24,
marginLeft: isSplit ? 0 : 5,
marginRight: isSplit ? 0 : 5,
},
key: `${btn.commandButtonLabel}${index}`,
text: label,
"data-test": label,
title: btn.tooltipText,
name: label,
disabled: btn.disabled,
ariaLabel: btn.ariaLabel,
buttonStyles: {
root: {
backgroundColor: backgroundColor,
height: buttonHeightPx,
paddingRight: 0,
paddingLeft: 0,
minWidth: 24,
marginLeft: isSplit ? 0 : 5,
marginRight: isSplit ? 0 : 5,
rootDisabled: {
backgroundColor: backgroundColor,
pointerEvents: "auto",
},
splitButtonMenuButton: {
backgroundColor: backgroundColor,
selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight },
},
rootDisabled: {
backgroundColor: backgroundColor,
pointerEvents: "auto",
width: 16,
},
label: { fontSize: StyleConstants.mediumFontSize },
rootHovered: { backgroundColor: StyleConstants.AccentLight },
rootPressed: { backgroundColor: StyleConstants.AccentLight },
splitButtonMenuButtonExpanded: {
backgroundColor: StyleConstants.AccentExtra,
selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight },
},
splitButtonMenuButton: {
backgroundColor: backgroundColor,
},
splitButtonDivider: {
display: "none",
},
icon: {
paddingLeft: 0,
paddingRight: 0,
},
splitButtonContainer: {
marginLeft: 5,
marginRight: 5,
},
},
className: btn.className,
id: btn.id,
};
if (isSplit) {
// It's a split button
result.split = true;
result.subMenuProps = {
items: convertButton(btn.children, backgroundColor),
styles: {
list: {
// TODO Figure out how to do it the proper way with subComponentStyles.
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight },
},
width: 16,
},
label: { fontSize: StyleConstants.mediumFontSize },
rootHovered: { backgroundColor: StyleConstants.AccentLight },
rootPressed: { backgroundColor: StyleConstants.AccentLight },
splitButtonMenuButtonExpanded: {
backgroundColor: StyleConstants.AccentExtra,
selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight },
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
".ms-ContextualMenu-icon": { width: 16, height: 16 },
},
},
splitButtonDivider: {
display: "none",
},
icon: {
paddingLeft: 0,
paddingRight: 0,
},
splitButtonContainer: {
marginLeft: 5,
marginRight: 5,
},
},
className: btn.className,
id: btn.id,
};
if (isSplit) {
// It's a split button
result.split = true;
result.menuIconProps = {
iconType: IconType.image,
style: {
width: 12,
paddingLeft: 1,
paddingTop: 6,
},
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt },
};
}
result.subMenuProps = {
items: convertButton(btn.children, backgroundColor),
styles: {
list: {
// TODO Figure out how to do it the proper way with subComponentStyles.
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
selectors: {
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
".ms-ContextualMenu-icon": { width: 16, height: 16 },
},
},
},
};
if (btn.isDropdown) {
const selectedChild = _.find(btn.children, (child) => child.dropdownItemKey === btn.dropdownSelectedKey);
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
result.menuIconProps = {
iconType: IconType.image,
style: {
width: 12,
paddingLeft: 1,
paddingTop: 6,
},
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt },
};
}
const dropdownStyles: Partial<IDropdownStyles> = {
root: { margin: 5 },
dropdown: { width: btn.dropdownWidth },
title: { fontSize: 12, height: 30, lineHeight: 28 },
dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 },
};
if (btn.isDropdown) {
const selectedChild = _.find(btn.children, (child) => child.dropdownItemKey === btn.dropdownSelectedKey);
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
const dropdownStyles: Partial<IDropdownStyles> = {
root: { margin: 5 },
dropdown: { width: btn.dropdownWidth },
title: { fontSize: 12, height: 30, lineHeight: 28 },
dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 },
};
const onDropdownChange = (
event: React.FormEvent<HTMLDivElement>,
option?: IDropdownOption,
index?: number
): void => {
btn.children[index].onCommandClick(event);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
};
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
return (
<Dropdown
placeholder={btn.dropdownPlaceholder}
defaultSelectedKey={btn.dropdownSelectedKey}
onChange={onDropdownChange}
options={btn.children.map((child: CommandButtonComponentProps) => ({
key: child.dropdownItemKey,
text: child.commandButtonLabel,
}))}
styles={dropdownStyles}
/>
);
};
}
const onDropdownChange = (
event: React.FormEvent<HTMLDivElement>,
option?: IDropdownOption,
index?: number
): void => {
btn.children[index].onCommandClick(event);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
};
return result;
}
);
return result;
});
};
export const createDivider = (key: string): ICommandBarItemProps => {

View File

@@ -0,0 +1,31 @@
/**
* React component for control bar
*/
import * as React from "react";
import {
CommandButtonComponent,
CommandButtonComponentProps,
} from "../../Controls/CommandButton/CommandButtonComponent";
export interface ControlBarComponentProps {
buttons: CommandButtonComponentProps[];
}
export class ControlBarComponent extends React.Component<ControlBarComponentProps> {
private static renderButtons(commandButtonOptions: CommandButtonComponentProps[]): JSX.Element[] {
return commandButtonOptions.map((btn: CommandButtonComponentProps, index: number): JSX.Element => {
// Remove label
btn.commandButtonLabel = undefined;
return CommandButtonComponent.renderButton(btn, `${index}`);
});
}
public render(): JSX.Element {
if (!this.props.buttons || this.props.buttons.length < 1) {
return <React.Fragment />;
}
return <React.Fragment>{ControlBarComponent.renderButtons(this.props.buttons)}</React.Fragment>;
}
}

View File

@@ -0,0 +1,28 @@
/**
* This adapter is responsible to render the React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import * as ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { ControlBarComponent } from "./ControlBarComponent";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class ControlBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private buttons: ko.ObservableArray<CommandButtonComponentProps>) {
this.buttons.subscribe(() => this.forceRender());
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <ControlBarComponent buttons={this.buttons()} />;
}
public forceRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -1,16 +0,0 @@
/**
* Interface for the data/content that will be recorded
*/
export interface ConsoleData {
type: ConsoleDataType;
date: string;
message: string;
id?: string;
}
export enum ConsoleDataType {
Info = 0,
Error = 1,
InProgress = 2,
}

View File

@@ -1,7 +1,10 @@
import { shallow } from "enzyme";
import React from "react";
import { ConsoleDataType } from "./ConsoleData";
import { NotificationConsoleComponent, NotificationConsoleComponentProps } from "./NotificationConsoleComponent";
import {
ConsoleDataType,
NotificationConsoleComponent,
NotificationConsoleComponentProps,
} from "./NotificationConsoleComponent";
describe("NotificationConsoleComponent", () => {
const createBlankProps = (): NotificationConsoleComponentProps => {

View File

@@ -3,21 +3,38 @@
*/
import { Dropdown, IDropdownOption } from "@fluentui/react";
import LoaderIcon from "images/circular_loader_black_16x16.gif";
import { ReactComponent as ClearIcon } from "images/Clear1.svg";
import ErrorBlackIcon from "images/error_black.svg";
import ErrorRedIcon from "images/error_red.svg";
import infoBubbleIcon from "images/info-bubble-9x9.svg";
import InfoIcon from "images/info_color.svg";
import LoadingIcon from "images/loading.svg";
import ChevronDownIcon from "images/QueryBuilder/CollapseChevronDown_16x.png";
import ChevronUpIcon from "images/QueryBuilder/CollapseChevronUp_16x.png";
import * as React from "react";
import AnimateHeight from "react-animate-height";
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
import ClearIcon from "../../../../images/Clear.svg";
import ErrorBlackIcon from "../../../../images/error_black.svg";
import ErrorRedIcon from "../../../../images/error_red.svg";
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
import InfoIcon from "../../../../images/info_color.svg";
import LoadingIcon from "../../../../images/loading.svg";
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { userContext } from "../../../UserContext";
import { ConsoleData, ConsoleDataType } from "./ConsoleData";
/**
* Log levels
*/
export enum ConsoleDataType {
Info = 0,
Error = 1,
InProgress = 2,
}
/**
* Interface for the data/content that will be recorded
*/
export interface ConsoleData {
type: ConsoleDataType;
date: string;
message: string;
id?: string;
}
export interface NotificationConsoleComponentProps {
isConsoleExpanded: boolean;
@@ -89,10 +106,12 @@ export class NotificationConsoleComponent extends React.Component<
const numInProgress = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.InProgress
).length;
const numErroredItems = this.state.allConsoleData.filter((data: ConsoleData) => data.type === ConsoleDataType.Error)
.length;
const numInfoItems = this.state.allConsoleData.filter((data: ConsoleData) => data.type === ConsoleDataType.Info)
.length;
const numErroredItems = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.Error
).length;
const numInfoItems = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.Info
).length;
return (
<div className="notificationConsoleContainer">
@@ -162,7 +181,7 @@ export class NotificationConsoleComponent extends React.Component<
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
tabIndex={0}
>
<img src={ClearIcon} alt="clear notifications image" />
<ClearIcon />
Clear Notifications
</span>
</div>
@@ -304,22 +323,3 @@ const PrPreview = (props: { pr: string }) => {
</>
);
};
export const NotificationConsole: React.FC = () => {
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
const isExpanded = useNotificationConsole((state) => state.isExpanded);
const consoleData = useNotificationConsole((state) => state.consoleData);
const inProgressConsoleDataIdToBeDeleted = useNotificationConsole(
(state) => state.inProgressConsoleDataIdToBeDeleted
);
// TODO Refactor NotificationConsoleComponent into a functional component and remove this wrapper
// This component only exists so we can use hooks and pass them down to a non-functional component
return (
<NotificationConsoleComponent
consoleData={consoleData}
inProgressConsoleDataIdToBeDeleted={inProgressConsoleDataIdToBeDeleted}
isConsoleExpanded={isExpanded}
setIsConsoleExpanded={setIsExpanded}
/>
);
};

View File

@@ -149,10 +149,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
role="button"
tabIndex={0}
>
<img
alt="clear notifications image"
src=""
/>
<Component />
Clear Notifications
</span>
</div>
@@ -315,10 +312,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
role="button"
tabIndex={0}
>
<img
alt="clear notifications image"
src=""
/>
<Component />
Clear Notifications
</span>
</div>

View File

@@ -21,7 +21,7 @@ import {
makeStateRecord,
makeTransformsRecord,
} from "@nteract/core";
import { configOption, defineConfigOption } from "@nteract/mythic-configuration";
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration";
import { Media } from "@nteract/outputs";
import TransformVDOM from "@nteract/transform-vdom";
import * as Immutable from "immutable";
@@ -192,36 +192,36 @@ export class NotebookClientV2 {
* is triggered for *any* state change).
* TODO: Use react-redux connect() to subscribe to state changes?
*/
const cacheKernelSpecsMiddleware: Middleware = <D extends Dispatch<AnyAction>, S extends AppState>({
dispatch,
getState,
}: MiddlewareAPI<D, S>) => (next: Dispatch<AnyAction>) => <A extends AnyAction>(action: A): A => {
switch (action.type) {
case actions.FETCH_KERNELSPECS_FULFILLED: {
const payload = ((action as unknown) as actions.FetchKernelspecsFulfilled).payload;
const defaultKernelName = payload.defaultKernelName;
this.kernelSpecsForDisplay = Object.values(payload.kernelspecs)
.filter((spec) => !spec.metadata?.hasOwnProperty("hidden"))
.map((spec) => ({
name: spec.name,
displayName: spec.displayName,
}))
.sort((a: KernelSpecsDisplay, b: KernelSpecsDisplay) => {
// Put default at the top, otherwise lexicographically compare
if (a.displayName === defaultKernelName) {
return -1;
} else if (b.name === defaultKernelName) {
return 1;
} else {
return a.displayName.localeCompare(b.displayName);
}
});
break;
const cacheKernelSpecsMiddleware: Middleware =
<D extends Dispatch<AnyAction>, S extends AppState>({ dispatch, getState }: MiddlewareAPI<D, S>) =>
(next: Dispatch<AnyAction>) =>
<A extends AnyAction>(action: A): A => {
switch (action.type) {
case actions.FETCH_KERNELSPECS_FULFILLED: {
const payload = (action as unknown as actions.FetchKernelspecsFulfilled).payload;
const defaultKernelName = payload.defaultKernelName;
this.kernelSpecsForDisplay = Object.values(payload.kernelspecs)
.filter((spec) => !spec.metadata?.hasOwnProperty("hidden"))
.map((spec) => ({
name: spec.name,
displayName: spec.displayName,
}))
.sort((a: KernelSpecsDisplay, b: KernelSpecsDisplay) => {
// Put default at the top, otherwise lexicographically compare
if (a.displayName === defaultKernelName) {
return -1;
} else if (b.name === defaultKernelName) {
return 1;
} else {
return a.displayName.localeCompare(b.displayName);
}
});
break;
}
}
}
return next(action);
};
return next(action);
};
const traceErrorFct = (title: string, message: string) => {
TelemetryProcessor.traceFailure(Action.NotebookErrorNotification, {
@@ -242,27 +242,22 @@ export class NotebookClientV2 {
);
// Additional configuration
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "codemirror"));
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "monaco"));
this.store.dispatch(
configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs)
);
this.store.dispatch(configOption("codeMirror.lineNumbers").action(true));
const readOnlyConfigOption = configOption("codeMirror.readOnly");
const readOnlyValue = params.isReadOnly ? "nocursor" : undefined;
if (!readOnlyConfigOption) {
defineConfigOption({
label: "Read-only",
key: "codeMirror.readOnly",
values: [
{ label: "Read-Only", value: "nocursor" },
{ label: "Not read-only", value: undefined },
],
defaultValue: readOnlyValue,
});
} else {
this.store.dispatch(readOnlyConfigOption.action(readOnlyValue));
}
createConfigCollection({
key: "monaco",
});
defineConfigOption({
label: "Show Line numbers",
key: "monaco.lineNumbers",
values: [
{ label: "Yes", value: true },
{ label: "No", value: false },
],
defaultValue: true,
});
}
/**

View File

@@ -1,10 +1,10 @@
.notebookComponentContainer {
text-transform: none;
line-height: 1.28581;
letter-spacing: 0;
font-size: 14px;
font-weight: 400;
color: #182026;
text-transform:none;
line-height:1.28581;
letter-spacing:0;
font-size:14px;
font-weight:400;
color:#182026;
height: 100%;
.hotKeys {

View File

@@ -9,7 +9,7 @@ import {
KernelRef,
RemoteKernelProps,
selectors,
ServerConfig as JupyterServerConfig
ServerConfig as JupyterServerConfig,
} from "@nteract/core";
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
import { RecordOf } from "immutable";
@@ -29,7 +29,7 @@ import {
switchMap,
take,
tap,
timeout
timeout,
} from "rxjs/operators";
import { webSocket } from "rxjs/webSocket";
import * as Constants from "../../../Common/Constants";
@@ -107,7 +107,7 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
const q = params.toString();
const suffix = q !== "" ? `?${q}` : "";
const url = (serverConfig.endpoint.slice(0, -1) || "") + `api/kernels/${kernelId}/channels${suffix}`;
const url = (serverConfig.endpoint || "") + `api/kernels/${kernelId}/channels${suffix}`;
return url.replace(/^http(s)?/, "ws$1");
};

View File

@@ -21,16 +21,16 @@ export default function configureStore(
/**
* Catches errors in reducers
*/
const catchErrorMiddleware: Middleware = <D extends Dispatch<AnyAction>, S extends AppState>({
dispatch,
getState,
}: MiddlewareAPI<D, S>) => (next: Dispatch<AnyAction>) => <A extends AnyAction>(action: A): any => {
try {
next(action);
} catch (error) {
traceFailure("Reducer failure", error);
}
};
const catchErrorMiddleware: Middleware =
<D extends Dispatch<AnyAction>, S extends AppState>({ dispatch, getState }: MiddlewareAPI<D, S>) =>
(next: Dispatch<AnyAction>) =>
<A extends AnyAction>(action: A): any => {
try {
next(action);
} catch (error) {
traceFailure("Reducer failure", error);
}
};
const protect = (epic: Epic) => {
return (action$: Observable<any>, state$: any, dependencies: any) =>

View File

@@ -53,7 +53,7 @@ export class NotebookContainerClient {
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
try {
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
method: "GET",
headers: {
Authorization: authToken,
@@ -130,23 +130,19 @@ export class NotebookContainerClient {
}
private async recreateNotebookWorkspaceAsync(): Promise<void> {
const explorer = window.dataExplorer;
const { databaseAccount } = userContext;
if (!databaseAccount?.id) {
throw new Error("DataExplorer not initialized");
}
/*
const notebookWorkspaceManager = explorer.notebookWorkspaceManager;
try {
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
await createOrUpdate(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
"default"
);
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(databaseAccount?.id, "default");
await notebookWorkspaceManager.createNotebookWorkspaceAsync(databaseAccount?.id, "default");
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
return Promise.reject(error);
}
*/
}
}

View File

@@ -12,7 +12,7 @@ export class NotebookContentClient {
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
public notebookBasePath: ko.Observable<string>,
private contentProvider: IContentProvider
) { }
) {}
/**
* This updates the item and points all the children's parent to this item
@@ -25,9 +25,6 @@ export class NotebookContentClient {
});
}
private sleep = (milliseconds: number) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
/**
*
* @param parent parent folder
@@ -38,7 +35,6 @@ export class NotebookContentClient {
}
const type = "notebook";
return this.contentProvider
.create<"notebook">(this.getServerConfig(), parent.path, { type })
.toPromise()

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