Compare commits

..

2 Commits

Author SHA1 Message Date
Victor Meng
acd5974024 Hard code to always open old mongo shell 2022-12-07 10:21:08 -08:00
Victor Meng
22f5fc13b2 Empty commit to trigger new build 2022-11-14 12:42:07 -08:00
80 changed files with 740 additions and 8064 deletions

View File

@@ -92,11 +92,11 @@ jobs:
name: dist name: dist
path: dist/ path: dist/
- name: Upload build to preview blob storage - name: Upload build to preview blob storage
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
env: env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
- name: Upload preview config to blob storage - name: Upload preview config to blob storage
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
env: env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator: endtoendemulator:
@@ -182,7 +182,7 @@ jobs:
with: with:
name: dist name: dist
- run: cp ./configs/prod.json config.json - run: cp ./configs/prod.json config.json
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT" - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "vimeng@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg - run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
@@ -207,7 +207,7 @@ jobs:
name: dist name: dist
- run: cp ./configs/mpac.json config.json - run: cp ./configs/mpac.json config.json
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec - run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT" - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "vimeng@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg - run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'> <!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg enable-background="new 0 0 256 256" height="256px" id="Layer_1" version="1.1" viewBox="0 0 256 256" width="256px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg enable-background="new 0 0 256 256" height="256px" id="Layer_1" version="1.1" viewBox="0 0 256 256" width="256px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path stroke="white" stroke-width="0.5" fill="#000" d="M179.199,38.399c0,1.637-0.625,3.274-1.875,4.524l-85.076,85.075l85.076,85.075c2.5,2.5,2.5,6.55,0,9.05s-6.55,2.5-9.05,0 l-89.601-89.6c-2.5-2.5-2.5-6.551,0-9.051l89.601-89.6c2.5-2.5,6.55-2.5,9.05,0C178.574,35.124,179.199,36.762,179.199,38.399z"/> <path stroke="white" stroke-width="0.5" fill="#b5a3a3" d="M179.199,38.399c0,1.637-0.625,3.274-1.875,4.524l-85.076,85.075l85.076,85.075c2.5,2.5,2.5,6.55,0,9.05s-6.55,2.5-9.05,0 l-89.601-89.6c-2.5-2.5-2.5-6.551,0-9.051l89.601-89.6c2.5-2.5,6.55-2.5,9.05,0C178.574,35.124,179.199,36.762,179.199,38.399z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -18,8 +18,7 @@ module.exports = {
// clearMocks: false, // clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test // Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
collectCoverage: process.env.skipCodeCoverage === "true" ? false : true,
// An array of glob patterns indicating a set of files for which coverage information should be collected // An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"], collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"],

View File

@@ -650,4 +650,4 @@ tr:hover td.nameColumnText {
.context-menu-item.icon-customize-columns { .context-menu-item.icon-customize-columns {
background-image: url(../../images/Options.svg); background-image: url(../../images/Options.svg);
} }

View File

@@ -3,337 +3,337 @@
@import "../Common/Constants"; @import "../Common/Constants";
.query-panel { .query-panel {
display: table; display: table;
display: none; display: none;
width: 100%; width: 100%;
border-top: 1px solid #dddddd; border-top: 1px solid #DDDDDD;
/*[{environment-commandbar-toolbar-separator}]*/ /*[{environment-commandbar-toolbar-separator}]*/
background-color: #ffffff; background-color: #ffffff;
/*[{plugin-background-color}]*/ /*[{plugin-background-color}]*/
padding: 2px 0px 0px 2px; padding: 2px 0px 0px 2px;
resize: vertical; resize: vertical;
} }
.query-panel .row { .query-panel .row {
display: table-row; display: table-row;
} }
.query-panel .row .cell { .query-panel .row .cell {
display: table-cell; display: table-cell;
} }
.query-panel.transition-in { .query-panel.transition-in {
display: table; display: table;
top: 0px; top: 0px;
-webkit-transition: top 2s linear; -webkit-transition: top 2s linear;
-ms-transition: top 2s linear; -ms-transition: top 2s linear;
-moz-transition: top 2s linear; -moz-transition: top 2s linear;
-khtml-transition: top 2s linear; -khtml-transition: top 2s linear;
-o-transition: top 2s linear; -o-transition: top 2s linear;
transition: top 2s linear; transition: top 2s linear;
} }
.query-builder { .query-builder {
width: 100%; width:100%;
padding-right: @DefaultSpace; padding-right: @DefaultSpace;
border-bottom: 1px solid @BaseMedium; border-bottom: 1px solid @BaseMedium;
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;
} }
.query-builder-toolbar { .query-builder-toolbar {
background-color: #ffffff; background-color: #ffffff;
/*[{plugin-background-color}]*/ /*[{plugin-background-color}]*/
min-width: 600px; min-width: 600px;
height: 30px; height: 30px;
border-bottom: 1px solid #dddddd; border-bottom: 1px solid #DDDDDD;
/*[1px solid {environment-commandbar-toolbar-separator}]*/ /*[1px solid {environment-commandbar-toolbar-separator}]*/
} }
.query-builder-toolbar .query-toolbar-group { .query-builder-toolbar .query-toolbar-group {
display: inline-block; display: inline-block;
height: 24px; height: 24px;
margin: 2px 0px; margin: 2px 0px;
vertical-align: middle; vertical-align: middle;
} }
.query-builder-toolbar .query-toolbar-group .query-toolbar-button { .query-builder-toolbar .query-toolbar-group .query-toolbar-button {
min-width: 0px; min-width: 0px;
padding: 0px; padding: 0px;
margin-left: 2px; margin-left: 2px;
background-color: transparent; background-color: transparent;
border: solid transparent; border: solid transparent;
} }
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active { .query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
outline: 2px solid dodgerblue; outline: 2px solid dodgerblue;
/*[2px solid {common-common-controls-button-border-hover}]*/ /*[2px solid {common-common-controls-button-border-hover}]*/
} }
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover { .query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
background-color: #cccedb; background-color: #CCCEDB;
/*[{common-controls-button-hover-background}]*/ /*[{common-controls-button-hover-background}]*/
} }
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active { .query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
background-color: #e6e7ed; background-color: #E6E7ED;
/*[{common-controls-inner-tab-active-background}]*/ /*[{common-controls-inner-tab-active-background}]*/
outline: none; outline: none
} }
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled, .query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled { .query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
background-color: #ffffff; background-color: #ffffff;
/*[{common-controls-button-disabled-background}]*/ /*[{common-controls-button-disabled-background}]*/
background: transparent; background: transparent;
border: 1px solid transparent; border: 1px solid transparent;
outline: none; outline: none;
opacity: 0.4; opacity: 0.4;
} }
.tableContainer { .tableContainer {
overflow: hidden; overflow: hidden;
.flex-display(); .flex-display();
.flex-direction(); .flex-direction();
} }
.tablesQueryTab { .tablesQueryTab{
padding-left: @MediumSpace; padding-left: @MediumSpace;
width: 100%; width: 100%;
margin-bottom: @LargeSpace; margin-bottom:@LargeSpace;
} }
.entity-error-Img { .entity-error-Img {
width: @WarningErrorIconSize; width: @WarningErrorIconSize;
height: @WarningErrorIconSize; height: @WarningErrorIconSize;
margin: @DefaultSpace 0px 0px @SmallSpace; margin: @DefaultSpace 0px 0px @SmallSpace;
} }
.query-editor-panel { .query-editor-panel {
margin-right: 16px; margin-right: 16px;
margin-left: 16px; margin-left: 16px;
margin-top: 25px; margin-top: 25px;
position: relative; position: relative;
vertical-align: middle; vertical-align: middle;
cursor: default; cursor: default;
} }
.query-editor-text { .query-editor-text {
width: 100%; width: 100%;
margin: 2px; margin: 2px;
border: solid 1px #a9acb3; border: solid 1px #A9ACB3;
/*[{plugin-textbox-disabled-color}]*/ /*[{plugin-textbox-disabled-color}]*/
resize: none; resize: none;
margin-top: -39px; margin-top: -39px;
background-color: #ddd; background-color: #ddd;
padding: 5px; padding: 5px;
} }
.error-bar { .error-bar {
padding: @LargeSpace 34px @MediumSpace 24px; padding: @LargeSpace 34px @MediumSpace 24px;
} }
.error-message { .error-message {
background-color: @BaseLow; background-color: @BaseLow;
padding: @DefaultSpace; padding: @DefaultSpace;
display: inline-flex; display: inline-flex;
} }
.error-text { .error-text {
padding-left: @MediumSpace; padding-left: @MediumSpace;
} }
.query-editor-text-invalid { .query-editor-text-invalid {
width: 100%; width: 100%;
margin: 2px; margin: 2px;
border: 1px solid #e51400; border: 1px solid #e51400;
resize: none; resize: none;
margin-top: -30px; margin-top: -30px;
} }
.query-editor-panel .warning-bar { .query-editor-panel .warning-bar {
width: 100%; width: 100%;
height: 20px; height: 20px;
background-color: #ffffff; background-color: #ffffff;
/*[{plugin-background-color}]*/ /*[{plugin-background-color}]*/
position: absolute; position: absolute;
top: -24px; top: -24px;
} }
.query-editor-panel .warning-bar .warning-message { .query-editor-panel .warning-bar .warning-message {
display: inline-flex; display: inline-flex;
padding-top: 2px; padding-top: 2px;
vertical-align: middle; vertical-align: middle;
} }
.query-editor-panel .warning-bar .warning-message .warning-text { .query-editor-panel .warning-bar .warning-message .warning-text {
margin-left: 2px; margin-left: 2px;
} }
.advanced-options-panel { .advanced-options-panel{
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;
} }
.advanced-options-panel .advanced-heading .advanced-title { .advanced-options-panel .advanced-heading .advanced-title {
display: inline-flex; display: inline-flex;
margin-left: 27px; margin-left: 27px;
margin-top: 10px; margin-top: 10px;
cursor: default; cursor: default;
} }
.advanced-options-panel .advanced-options { .advanced-options-panel .advanced-options {
margin-left: 32px; margin-left: 32px;
margin-top: 5px; margin-top: 5px;
border: 1px solid transparent; border: 1px solid transparent;
} }
hr { hr {
margin-top: 10px; margin-top: 10px;
margin-bottom: 12px; margin-bottom: 12px;
border: 0; border: 0;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
} }
input::-webkit-outer-spin-button, input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button { input::-webkit-inner-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
} }
.advanced-options-panel .advanced-options .top .top-input { .advanced-options-panel .advanced-options .top .top-input {
width: 100px; width: 100px;
word-spacing: normal; word-spacing: normal;
color: #1e1e1e; color: #1E1E1E;
/*[{common-controls-button-foreground}]*/ /*[{common-controls-button-foreground}]*/
border: 1px solid #cccedb; border: 1px solid #CCCEDB;
/*[1px solid {plugin-textbox-border-color}]*/ /*[1px solid {plugin-textbox-border-color}]*/
height: 20px; height: 20px;
margin-left: 8px; margin-left: 8px;
} }
.advanced-options-panel .advanced-options .top .invalid-top { .advanced-options-panel .advanced-options .top .invalid-top {
color: red; color: red;
} }
.advanced-options-panel .advanced-options .select { .advanced-options-panel .advanced-options .select {
margin-top: 18px; margin-top: 18px;
display: inline-flex; display: inline-flex;
} }
.advanced-options-icon { .advanced-options-icon {
margin-left: 2px; margin-left: 2px;
vertical-align: sub; vertical-align: sub;
} }
.advanced-options-panel .advanced-options .select .select-options-text { .advanced-options-panel .advanced-options .select .select-options-text {
margin-left: 4px; margin-left: 4px;
} }
.advanced-options-panel .advanced-options .select .select-options-link { .advanced-options-panel .advanced-options .select .select-options-link {
margin-left: 4px; margin-left: 4px;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
} }
.query-panel .row .column-headers .Field { .query-panel .row .column-headers .Field {
padding-left: 95px; padding-left: 95px;
padding-right: 0px; padding-right: 0px;
padding-bottom: 6px; padding-bottom: 6px;
} }
.clause-table { .clause-table {
border-spacing: 0px; border-spacing: 0px;
display: table; display: table;
width: 100%; width: 100%;
margin-top: -3px; margin-top: -3px;
} }
.clause-table-row { .clause-table-row {
display: row; display: row;
margin-bottom: 10px; margin-bottom: 10px;
} }
.clause-table-cell { .clause-table-cell {
display: table-cell; display: table-cell;
text-align: left; text-align: left;
vertical-align: middle; vertical-align: middle;
} }
.action-column > button, .action-column>button,
.group-control-header > button, .group-control-header>button,
.group-indicator-column > button { .group-indicator-column>button {
min-width: 20px; min-width: 20px;
width: 20px; width: 20px;
padding: 0px; padding: 0px;
background-color: transparent; background-color: transparent;
border-color: transparent; border-color: transparent;
cursor: pointer; cursor: pointer;
} }
.group-control-header > button:disabled { .group-control-header>button:disabled {
min-width: 20px; min-width: 20px;
width: 20px; width: 20px;
padding: 0px; padding: 0px;
background-color: transparent; background-color: transparent;
border-color: transparent; border-color: transparent;
outline: none; outline: none;
opacity: 0.4; opacity: 0.4;
cursor: default; cursor: default;
} }
.clause-table-field { .clause-table-field {
width: 100%; width: 100%;
border: 1px solid #bbbbbb; border: 1px solid #bbbbbb;
} }
.clause-table-cell button { .clause-table-cell button {
height: 20px; height: 20px;
} }
.clause-table-cell input[type="checkbox"] { .clause-table-cell input[type="checkbox"] {
padding: 0px; padding: 0px;
margin-bottom: 12px; margin-bottom: 12px;
} }
.and-or-svg { .and-or-svg {
margin-top: -8px; margin-top: -8px;
margin-right: -5px; margin-right: -5px;
} }
.scroll-box { .scroll-box {
border-bottom: 1px transparent #ddd; border-bottom: 1px transparent #DDD;
/*[1px solid {plugin-table-border-color}]*/ /*[1px solid {plugin-table-border-color}]*/
border-top: 1px transparent #ddd; border-top: 1px transparent #DDD;
/*[1px solid {plugin-table-border-color}]*/ /*[1px solid {plugin-table-border-color}]*/
max-height: 20vh; max-height: 20vh;
width: 100%; width: 100%;
} }
.scrollable { .scrollable {
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
} }
.and-or-column, .and-or-column,
.and-or-header { .and-or-header {
min-width: 65px; min-width: 65px;
padding-right: 10px; padding-right: 10px;
padding-left: 5px; padding-left: 5px;
} }
.operator-column, .operator-column,
.operator-header { .operator-header {
min-width: 65px; min-width: 65px;
padding-right: 10px; padding-right: 10px;
} }
.field-header, .field-header,
.field-column { .field-column {
min-width: 125px; min-width: 125px;
padding-right: 10px; padding-right: 10px;
} }
.type-header, .type-header,
.type-column { .type-column {
min-width: 85px; min-width: 85px;
} }
.and-or-column, .and-or-column,
@@ -345,41 +345,41 @@ input::-webkit-inner-spin-button {
.type-header, .type-header,
.type-column, .type-column,
.action-header { .action-header {
padding-right: 10px; padding-right: 10px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.value-header, .value-header,
.value-column, .value-column,
.time-column { .time-column {
min-width: 230px; min-width: 230px;
padding: 0px 4px 0px 0px; padding: 0px 4px 0px 0px;
width: 100%; width: 100%;
margin-bottom: 8px; margin-bottom: 8px;
} }
.group-control-header, .group-control-header,
.group-control-column { .group-control-column {
min-width: 25px; min-width: 25px;
text-align: right; text-align: right;
} }
.group-indicator-table { .group-indicator-table {
border-spacing: 0px; border-spacing: 0px;
min-height: 24px; min-height: 24px
} }
.group-indicator-column { .group-indicator-column {
min-width: 21px; min-width: 21px;
padding: 0px; padding: 0px;
border-style: none; border-style: none;
height: 29px; height: 29px;
} }
.clause-table-cell.action-column, .clause-table-cell.action-column,
.clause-table-cell.action-header { .clause-table-cell.action-header {
min-width: 60px; min-width: 60px;
padding-left: @SmallSpace; padding-left: @SmallSpace;
} }
.action-header, .action-header,
@@ -388,14 +388,15 @@ input::-webkit-inner-spin-button {
.operator-header, .operator-header,
.value-header, .value-header,
.and-or-header { .and-or-header {
padding-right: 4px; padding-right: 4px;
padding-bottom: 5px; padding-bottom: 5px;
} }
.header-background { .header-background {
background-color: #ffffff; background-color: #ffffff;
} }
/*.type-header { /*.type-header {
padding-right: 4px; padding-right: 4px;
} }
@@ -409,111 +410,112 @@ input::-webkit-inner-spin-button {
}*/ }*/
.clause-table-field[readonly] { .clause-table-field[readonly] {
background-color: #eeeef2; background-color: #EEEEF2;
/*[{plugin-table-header-background-color}]*/ /*[{plugin-table-header-background-color}]*/
border: 1px solid #cccedb; border: 1px solid #CCCEDB;
/*[{plugin-table-border-color}]*/ /*[{plugin-table-border-color}]*/
} }
.addClause-title { .addClause-title {
/*[{common-common-controls-button-border-hover}]*/ /*[{common-common-controls-button-border-hover}]*/
cursor: pointer; cursor: pointer;
margin-left: -5px; margin-left: -5px;
} }
.addClause { .addClause {
width: 125px; width: 125px;
padding: 8px 0px 5px 5px; padding: 8px 0px 5px 5px;
border: 1px solid #fff; border: 1px solid #fff;
margin-left: 5px; margin-left: 5px;
} }
.addClause:hover { .addClause:hover {
.hover(); .hover();
} }
.addClause:active { .addClause:active {
.active(); .active();
border: 1px dashed @AccentMedium; border: 1px dashed @AccentMedium;
} }
.clause-table-row { .clause-table-row {
min-width: 550px; min-width: 550px;
width: 100%; width: 100%;
} }
.clause-table-field field-column { .clause-table-field field-column {
min-width: 75px; min-width: 75px;
height: 30px; height: 30px;
width: 100%; width: 100%;
} }
.clause-table-field field-input { .clause-table-field field-input {
min-width: 54px; min-width: 54px;
margin-left: -78px; margin-left: -78px;
height: 25px; height: 25px;
border: none; border: none;
} }
.query-panel .row .spacing { .query-panel .row .spacing {
padding-bottom: 6px; padding-bottom: 6px;
} }
.query-panel .divider.horizontal { .query-panel .divider.horizontal {
height: 10px; height: 10px;
width: 100%; width: 100%
} }
.inline-div { .inline-div {
display: inline; display: inline
} }
.querybuilder-addpropertyImg, .querybuilder-addpropertyImg,
.querybuilder-cancelImg { .querybuilder-cancelImg {
width: 14px; width: 14px;
height: 14px; height: 14px;
margin-left: 3px; margin-left: 3px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.addclauseProperty-Img { .addclauseProperty-Img {
width: 14px; width: 14px;
height: 14px; height: 14px;
margin-bottom: 5px; margin-bottom: 5px;
margin-left: 12px; margin-left: 12px;
} }
.entity-Add-Cancel { .entity-Add-Cancel {
// padding: @DefaultSpace @SmallSpace @SmallSpace; padding: @DefaultSpace @SmallSpace @SmallSpace;
cursor: pointer; cursor: pointer;
} }
.entity-Add-Cancel:hover { .entity-Add-Cancel:hover {
.hover(); .hover();
} }
.entity-Add-Cancel:active { .entity-Add-Cancel:active {
.active(); .active();
} }
.query-builder-isDisabled { .query-builder-isDisabled {
border: 1px solid #cccedb; border: 1px solid #CCCEDB;
color: #ccc; color: #ccc;
} }
.edit-value-text { .edit-value-text {
padding-left: @DefaultSpace; padding-left: @DefaultSpace;
} }
.expand-triangle { .expand-triangle {
width: 10px; width: 10px;
height: 10px; height: 10px;
} }
.expand-triangle-right { .expand-triangle-right {
margin-bottom: 5px; margin-bottom: 5px;
} }
/* /*
@media only screen and (max-width: 1200px) { @media only screen and (max-width: 1200px) {
.clause-table { .clause-table {
@@ -522,4 +524,4 @@ input::-webkit-inner-spin-button {
width: 100%; width: 100%;
padding-top: 10px; padding-top: 10px;
} }
}*/ }*/

View File

@@ -2576,10 +2576,6 @@ a:link {
.querydropdown.placeholderVisible { .querydropdown.placeholderVisible {
font-style: italic; font-style: italic;
} }
.querydropdown.placeholderVisible::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: #767474;
opacity: 1;
}
.querydropdown:hover { .querydropdown:hover {
background-color: @AccentLow; background-color: @AccentLow;
@@ -3091,4 +3087,3 @@ a:link {
background: white; background: white;
height: 100%; height: 100%;
} }

View File

@@ -5,17 +5,12 @@
overflow: auto; overflow: auto;
.databaseHeader { .databaseHeader {
padding: 1px;
font-size: 14px; font-size: 14px;
} }
.collectionHeader { .collectionHeader {
font-size: 12px; font-size: 12px;
} }
.loadMoreHeader {
color: RGB(5, 99, 193);
}
} }
.notebookResourceTree { .notebookResourceTree {
@@ -28,4 +23,6 @@
.clickDisabled { .clickDisabled {
pointer-events: none; pointer-events: none;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
id="collapseToggleLeftPaneButton" id="collapseToggleLeftPaneButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={getApiShortDisplayName() + `Expand tree`} aria-label="Expand Tree"
onClick={toggleLeftPaneExpanded} onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded} onKeyPress={onKeyPressToggleLeftPaneExpanded}
ref={focusButton} ref={focusButton}

View File

@@ -45,8 +45,6 @@ export class ArmResourceTypes {
export class BackendDefaults { export class BackendDefaults {
public static partitionKeyKind = "Hash"; public static partitionKeyKind = "Hash";
public static partitionKeyMultiHash = "MultiHash";
public static maxNumMultiHashPartition = 2;
public static singlePartitionStorageInGb: string = "10"; public static singlePartitionStorageInGb: string = "10";
public static multiPartitionStorageInGb: string = "100"; public static multiPartitionStorageInGb: string = "100";
public static maxChangeFeedRetentionDuration: number = 10; public static maxChangeFeedRetentionDuration: number = 10;
@@ -141,7 +139,7 @@ export class Queries {
public static UnlimitedPageOption: string = "unlimited"; public static UnlimitedPageOption: string = "unlimited";
public static itemsPerPage: number = 100; public static itemsPerPage: number = 100;
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
public static containersPerPage: number = 50;
public static QueryEditorMinHeightRatio: number = 0.1; public static QueryEditorMinHeightRatio: number = 0.1;
public static QueryEditorMaxHeightRatio: number = 0.4; public static QueryEditorMaxHeightRatio: number = 0.4;
public static readonly DefaultMaxDegreeOfParallelism = 6; public static readonly DefaultMaxDegreeOfParallelism = 6;

View File

@@ -55,6 +55,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
label={entityValueLabel && entityValueLabel} label={entityValueLabel && entityValueLabel}
className="addEntityTextField" className="addEntityTextField"
id="entityValueId" id="entityValueId"
autoFocus
disabled={isEntityValueDisable} disabled={isEntityValueDisable}
type={entityValueType} type={entityValueType}
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}

View File

@@ -1,14 +0,0 @@
import * as EnvironmentUtility from "./EnvironmentUtility";
describe("Environment Utility Test", () => {
it("Test sample URI with /", () => {
const uri = "test/";
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(uri);
});
it("Test sample URI without /", () => {
const uri = "test";
const expectedResult = "test/";
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult);
});
});

View File

@@ -1,6 +1,6 @@
import { OfferResponse } from "@azure/cosmos";
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
import * as OfferUtility from "./OfferUtility"; import * as OfferUtility from "./OfferUtility";
import { SDKOfferDefinition, Offer } from "../Contracts/DataModels";
import { OfferResponse } from "@azure/cosmos";
describe("parseSDKOfferResponse", () => { describe("parseSDKOfferResponse", () => {
it("manual throughput", () => { it("manual throughput", () => {
@@ -31,26 +31,6 @@ describe("parseSDKOfferResponse", () => {
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult); expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
}); });
it("offerContent not defined", () => {
const mockOfferDefinition = {
id: "test",
} as SDKOfferDefinition;
const mockResponse = {
resource: mockOfferDefinition,
} as OfferResponse;
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(undefined);
});
it("offerDefinition is null", () => {
const mockResponse = {
resource: undefined,
} as OfferResponse;
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(undefined);
});
it("autoscale throughput", () => { it("autoscale throughput", () => {
const mockOfferDefinition = { const mockOfferDefinition = {
content: { content: {

View File

@@ -51,7 +51,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
role="button" role="button"
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }" data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
tabIndex={0} tabIndex={0}
aria-label={getApiShortDisplayName() + `Refresh tree`} aria-label="Refresh tree"
title="Refresh tree" title="Refresh tree"
> >
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" /> <img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
@@ -63,7 +63,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
onClick={toggleLeftPaneExpanded} onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded} onKeyPress={onKeyPressToggleLeftPaneExpanded}
tabIndex={0} tabIndex={0}
aria-label={getApiShortDisplayName() + `Collapse Tree`} aria-label="Collapse Tree"
title="Collapse Tree" title="Collapse Tree"
ref={focusButton} ref={focusButton}
> >

View File

@@ -73,17 +73,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
const sectionStackTokens: IStackTokens = { childrenGap: 12 }; const sectionStackTokens: IStackTokens = { childrenGap: 12 };
const handleKeyPress = (event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter" || event.key === "Space") {
onEditEntity();
}
};
const handleKeyPressdelete = (event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter" || event.key === "Space") {
onDeleteEntity();
}
};
const getEntityValueType = (): string => { const getEntityValueType = (): string => {
const { Int, Smallint, Tinyint } = CassandraType; const { Int, Smallint, Tinyint } = CassandraType;
const { Double, Int32, Int64 } = TableType; const { Double, Int32, Int64 } = TableType;
@@ -137,29 +126,12 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
/> />
{!isEntityValueDisable && ( {!isEntityValueDisable && (
<TooltipHost content="Edit property" id="editTooltip"> <TooltipHost content="Edit property" id="editTooltip">
<div tabIndex={0}> <Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
<Image
{...imageProps}
src={EditIcon}
alt="editEntity"
onClick={onEditEntity}
tabIndex={0}
onKeyPress={handleKeyPress}
/>
</div>
</TooltipHost> </TooltipHost>
)} )}
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && ( {isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
<TooltipHost content="Delete property" id="deleteTooltip"> <TooltipHost content="Delete property" id="deleteTooltip">
<Image <Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
{...imageProps}
src={DeleteIcon}
alt="delete entity"
id="deleteEntity"
onClick={onDeleteEntity}
tabIndex={0}
onKeyPress={handleKeyPressdelete}
/>
</TooltipHost> </TooltipHost>
)} )}
</Stack> </Stack>

View File

@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
return ( return (
<span> <span>
<TooltipHost content={children}> <TooltipHost content={children}>
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost> </TooltipHost>
</span> </span>
); );

View File

@@ -1,49 +0,0 @@
import * as UrlUtility from "./UrlUtility";
describe("parseDocumentsPath", () => {
it("empty resource path", () => {
const resourcePath = "";
expect(UrlUtility.parseDocumentsPath(resourcePath)).toEqual({});
});
it("resourcePath does not begin or end with /", () => {
const resourcePath = "localhost/portal/home";
const expectedResult = {
type: "home",
objectBody: {
id: "portal",
self: "/localhost/portal/home/",
},
};
expect(UrlUtility.parseDocumentsPath(resourcePath)).toEqual(expectedResult);
});
it("resourcePath length is even", () => {
const resourcePath = "/localhost/portal/src/home/";
const expectedResult = {
type: "src",
objectBody: {
id: "home",
self: resourcePath,
},
};
expect(UrlUtility.parseDocumentsPath(resourcePath)).toEqual(expectedResult);
});
it("createUri", () => {
const baseUri = "http://foo.com/bar/";
const relativeUri = "/index.html";
const expectedUri = "http://foo.com/bar/index.html";
expect(UrlUtility.createUri(baseUri, relativeUri)).toEqual(expectedUri);
});
it("should throw an error if baseUri is empty", () => {
expect(() => {
UrlUtility.createUri("", "/home");
}).toThrow("baseUri is null or empty");
});
});

View File

@@ -4,7 +4,6 @@ import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationCons
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility"; import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { getPartitionKeyValue } from "./getPartitionKeyValue";
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => { export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
const entityName: string = getEntityName(); const entityName: string = getEntityName();
@@ -14,7 +13,7 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
await client() await client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.item(documentId.id(), getPartitionKeyValue(documentId)) .item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
.delete(); .delete();
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`); logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
} catch (error) { } catch (error) {

View File

@@ -1,12 +0,0 @@
import { userContext } from "UserContext";
import DocumentId from "../../Explorer/Tree/DocumentId";
export const getPartitionKeyValue = (documentId: DocumentId) => {
if (userContext.apiType === "Tables" && documentId.partitionKeyValue?.length === 0) {
return "";
}
if (documentId.partitionKeyValue?.length === 0) {
return undefined;
}
return documentId.partitionKeyValue;
};

View File

@@ -1,4 +1,3 @@
import { Queries } from "Common/Constants";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
@@ -32,35 +31,6 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
} }
} }
export async function readCollectionsWithPagination(
databaseId: string,
continuationToken?: string
): Promise<DataModels.CollectionsWithPagination> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
const sdkResponse = await client()
.database(databaseId)
.containers.query(
{ query: "SELECT * FROM c" },
{
continuationToken,
maxItemCount: Queries.containersPerPage,
}
)
.fetchNext();
const collectionsWithPagination: DataModels.CollectionsWithPagination = {
collections: sdkResponse.resources as DataModels.Collection[],
continuationToken: sdkResponse.continuationToken,
};
return collectionsWithPagination;
} catch (error) {
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
throw error;
} finally {
clearMessage();
}
}
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> { async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
let rpResponse; let rpResponse;

View File

@@ -6,7 +6,6 @@ import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility"; import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { getPartitionKeyValue } from "./getPartitionKeyValue";
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => { export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
const entityName = getEntityName(); const entityName = getEntityName();
@@ -22,7 +21,8 @@ export const readDocument = async (collection: CollectionBase, documentId: Docum
const response = await client() const response = await client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.item(documentId.id(), getPartitionKeyValue(documentId)) // use undefined if the partitionKeyValue is empty
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
.read(options); .read(options);
return response?.resource; return response?.resource;

View File

@@ -6,7 +6,6 @@ import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationCons
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility"; import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { getPartitionKeyValue } from "./getPartitionKeyValue";
export const updateDocument = async ( export const updateDocument = async (
collection: CollectionBase, collection: CollectionBase,
@@ -26,7 +25,7 @@ export const updateDocument = async (
const response = await client() const response = await client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.item(documentId.id(), getPartitionKeyValue(documentId)) .item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
.replace(newDocument, options); .replace(newDocument, options);
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`); logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);

View File

@@ -156,11 +156,6 @@ export interface Collection extends Resource {
requestSchema?: () => void; requestSchema?: () => void;
} }
export interface CollectionsWithPagination {
continuationToken?: string;
collections?: Collection[];
}
export interface Database extends Resource { export interface Database extends Resource {
collections?: Collection[]; collections?: Collection[];
} }

View File

@@ -87,13 +87,13 @@ export interface Database extends TreeNode {
isDatabaseExpanded: ko.Observable<boolean>; isDatabaseExpanded: ko.Observable<boolean>;
isDatabaseShared: ko.Computed<boolean>; isDatabaseShared: ko.Computed<boolean>;
isSampleDB?: boolean; isSampleDB?: boolean;
collectionsContinuationToken?: string;
selectedSubnodeKind: ko.Observable<CollectionTabKind>; selectedSubnodeKind: ko.Observable<CollectionTabKind>;
expandDatabase(): Promise<void>; expandDatabase(): Promise<void>;
collapseDatabase(): void; collapseDatabase(): void;
loadCollections(restart?: boolean): Promise<void>; loadCollections(): Promise<void>;
findCollectionWithId(collectionId: string): Collection; findCollectionWithId(collectionId: string): Collection;
openAddCollection(database: Database, event: MouseEvent): void; openAddCollection(database: Database, event: MouseEvent): void;
onSettingsClick: () => void; onSettingsClick: () => void;
@@ -397,7 +397,6 @@ export interface DataExplorerInputsFrame {
defaultCollectionThroughput?: CollectionCreationDefaults; defaultCollectionThroughput?: CollectionCreationDefaults;
isPostgresAccount?: boolean; isPostgresAccount?: boolean;
isReplica?: boolean; isReplica?: boolean;
clientIpAddress?: string;
// TODO: Update this param in the OSS extension to remove isFreeTier, isMarlinServerGroup, and make nodes a flat array instead of an nested array // TODO: Update this param in the OSS extension to remove isFreeTier, isMarlinServerGroup, and make nodes a flat array instead of an nested array
connectionStringParams?: any; connectionStringParams?: any;
flights?: readonly string[]; flights?: readonly string[];

View File

@@ -85,11 +85,7 @@ export const createCollectionContextMenuButton = (
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { selectedCollection && selectedCollection.onNewMongoShellClick();
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
selectedCollection && selectedCollection.onNewMongoShellClick();
}
}, },
label: useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell", label: useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell",
}); });

View File

@@ -73,7 +73,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
<img <img
className="expandCollapseIcon" className="expandCollapseIcon"
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon} src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
alt={this.state.isExpanded ? `${this.props.title} hide` : `${this.props.title} expand`} alt="Hide"
tabIndex={0} tabIndex={0}
role="button" role="button"
/> />

View File

@@ -1,5 +1,5 @@
import { HoverCard, HoverCardType, Icon, Label, Link, Stack } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "@fluentui/react";
import { CodeOfConductEndpoints } from "../../../../Common/Constants"; import { CodeOfConductEndpoints } from "../../../../Common/Constants";
import "./InfoComponent.less"; import "./InfoComponent.less";
@@ -41,7 +41,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<HoverCard plainCardProps={{ onRenderPlainCard: this.onHover }} instantOpenOnClick type={HoverCardType.plain}> <HoverCard plainCardProps={{ onRenderPlainCard: this.onHover }} instantOpenOnClick type={HoverCardType.plain}>
<div className="infoPanelMain" tabIndex={0}> <div className="infoPanelMain">
<Icon className="infoIconMain" iconName="Help" styles={{ root: { verticalAlign: "middle" } }} /> <Icon className="infoIconMain" iconName="Help" styles={{ root: { verticalAlign: "middle" } }} />
<Label className="infoLabelMain">Help</Label> <Label className="infoLabelMain">Help</Label>
</div> </div>

View File

@@ -12,7 +12,6 @@ exports[`InfoComponent renders 1`] = `
> >
<div <div
className="infoPanelMain" className="infoPanelMain"
tabIndex={0}
> >
<Icon <Icon
className="infoIconMain" className="infoIconMain"

View File

@@ -310,9 +310,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
/> />
)} )}
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && ( {this.isLargePartitionKeyEnabled() && <Text>Large {this.partitionKeyName.toLowerCase()} has been enabled</Text>}
<Text>Large {this.partitionKeyName.toLowerCase()} has been enabled</Text>
)}
</Stack> </Stack>
); );

View File

@@ -82,6 +82,7 @@ interface ThroughputInputAutoPilotV3State {
spendAckChecked: boolean; spendAckChecked: boolean;
exceedFreeTierThroughput: boolean; exceedFreeTierThroughput: boolean;
} }
export class ThroughputInputAutoPilotV3Component extends React.Component< export class ThroughputInputAutoPilotV3Component extends React.Component<
ThroughputInputAutoPilotV3Props, ThroughputInputAutoPilotV3Props,
ThroughputInputAutoPilotV3State ThroughputInputAutoPilotV3State
@@ -623,10 +624,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
return ( return (
<> <>
{warningMessage && ( {warningMessage && (
<MessageBar <MessageBar messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}>
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
role="alert"
>
{warningMessage} {warningMessage}
</MessageBar> </MessageBar>
)} )}

View File

@@ -15,7 +15,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
"iconName": "WarningSolid", "iconName": "WarningSolid",
} }
} }
role="alert"
> >
<Text <Text
styles={ styles={

View File

@@ -46,13 +46,10 @@ export class TabComponent extends React.Component<TabComponentProps> {
} }
let className = "toggleSwitch"; let className = "toggleSwitch";
let ariaselected;
if (index === this.props.currentTabIndex) { if (index === this.props.currentTabIndex) {
className += " selectedToggle"; className += " selectedToggle";
ariaselected = true;
} else { } else {
className += " unselectedToggle"; className += " unselectedToggle";
ariaselected = false;
} }
return ( return (
@@ -60,10 +57,9 @@ export class TabComponent extends React.Component<TabComponentProps> {
<AccessibleElement <AccessibleElement
as="span" as="span"
className={className} className={className}
role="tab" role="presentation"
onActivated={() => this.setActiveTab(index)} onActivated={() => this.setActiveTab(index)}
aria-label={`Select tab: ${tab.title}`} aria-label={`Select tab: ${tab.title}`}
aria-selected={ariaselected}
> >
{tab.title} {tab.title}
</AccessibleElement> </AccessibleElement>
@@ -81,11 +77,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
return ( return (
<div className="tabComponentContainer"> <div className="tabComponentContainer">
{!this.props.hideHeader && ( {!this.props.hideHeader && <div className="tabs tabSwitch">{this.renderTabTitles()}</div>}
<div className="tabs tabSwitch" role="tablist">
{this.renderTabTitles()}
</div>
)}
<div className={className}>{currentTabContent.render()}</div> <div className={className}>{currentTabContent.render()}</div>
</div> </div>
); );

View File

@@ -14,7 +14,3 @@
.throughputInputSpacing > :not(:last-child) { .throughputInputSpacing > :not(:last-child) {
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;
} }
.capacitycalculator-link:focus{
text-decoration: underline;
outline-offset: 2px;
}

View File

@@ -23,12 +23,12 @@ describe("ThroughputInput Pane", () => {
}); });
it("should switch mode properly", () => { it("should switch mode properly", () => {
wrapper.find('[aria-label="Manual database throughput"]').simulate("change"); wrapper.find('[aria-label="Manual mode"]').simulate("change");
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe( expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe(
"Container throughput (400 - unlimited RU/s)" "Container throughput (400 - unlimited RU/s)"
); );
wrapper.find('[aria-label="Autoscale database throughput"]').simulate("change"); wrapper.find('[aria-label="Autoscale mode"]').simulate("change");
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe("Container throughput (autoscale)"); expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe("Container throughput (autoscale)");
}); });
}); });

View File

@@ -185,49 +185,36 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack> </Stack>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<div role="radiogroup"> <input
<input className="throughputInputRadioBtn"
id="Autoscale-input" aria-label="Autoscale mode"
className="throughputInputRadioBtn" aria-required={true}
aria-label="Autoscale database throughput" checked={isAutoscaleSelected}
aria-required={true} type="radio"
checked={isAutoscaleSelected} role="radio"
type="radio" tabIndex={0}
role="radio" onChange={(e) => handleOnChangeMode(e, "Autoscale")}
tabIndex={0} />
onChange={(e) => handleOnChangeMode(e, "Autoscale")} <span className="throughputInputRadioBtnLabel">Autoscale</span>
/>
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
Autoscale
</label>
<input <input
id="Manual-input" className="throughputInputRadioBtn"
className="throughputInputRadioBtn" aria-label="Manual mode"
aria-label="Manual database throughput" checked={!isAutoscaleSelected}
checked={!isAutoscaleSelected} type="radio"
type="radio" aria-required={true}
aria-required={true} role="radio"
role="radio" tabIndex={0}
tabIndex={0} onChange={(e) => handleOnChangeMode(e, "Manual")}
onChange={(e) => handleOnChangeMode(e, "Manual")} />
/> <span className="throughputInputRadioBtnLabel">Manual</span>
<label className="throughputInputRadioBtnLabel" htmlFor="Manual-input">
Manual
</label>
</div>
</Stack> </Stack>
{isAutoscaleSelected && ( {isAutoscaleSelected && (
<Stack className="throughputInputSpacing"> <Stack className="throughputInputSpacing">
<Text variant="small" aria-label="capacity calculator of azure cosmos db"> <Text variant="small" aria-label="ruDescription">
Estimate your required RU/s with{" "} Estimate your required RU/s with{" "}
<Link <Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="ruDescription">
target="_blank"
href="https://cosmos.azure.com/capacitycalculator/"
aria-label="capacity calculator of azure cosmos db"
className="capacitycalculator-link"
>
capacity calculator capacity calculator
</Link> </Link>
. .
@@ -251,7 +238,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
step={AutoPilotUtils.autoPilotIncrementStep} step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.autoPilotThroughput1K} min={AutoPilotUtils.autoPilotThroughput1K}
value={throughput.toString()} value={throughput.toString()}
ariaLabel={`${isDatabase ? "Database" : getCollectionName()} max RU/s`} aria-label="Max request units per second"
required={true} required={true}
errorMessage={throughputError} errorMessage={throughputError}
/> />

View File

@@ -344,13 +344,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
onMouseLeave={[Function]} onMouseLeave={[Function]}
> >
<StyledIconBase <StyledIconBase
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage." ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
> >
<IconBase <IconBase
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage." ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
styles={[Function]} styles={[Function]}
@@ -630,7 +630,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
} }
> >
<i <i
aria-label="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage." aria-label="Info"
className="panelInfoIcon root-57" className="panelInfoIcon root-57"
data-icon-name="Info" data-icon-name="Info"
role="img" role="img"
@@ -654,45 +654,40 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<div <div
className="ms-Stack css-58" className="ms-Stack css-58"
> >
<div <input
aria-label="Autoscale mode"
aria-required={true}
checked={true}
className="throughputInputRadioBtn"
key=".0:$.0" key=".0:$.0"
role="radiogroup" onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="throughputInputRadioBtnLabel"
key=".0:$.1"
> >
<input Autoscale
aria-label="Autoscale database throughput" </span>
aria-required={true} <input
checked={true} aria-label="Manual mode"
className="throughputInputRadioBtn" aria-required={true}
id="Autoscale-input" checked={false}
onChange={[Function]} className="throughputInputRadioBtn"
role="radio" key=".0:$.2"
tabIndex={0} onChange={[Function]}
type="radio" role="radio"
/> tabIndex={0}
<label type="radio"
className="throughputInputRadioBtnLabel" />
htmlFor="Autoscale-input" <span
> className="throughputInputRadioBtnLabel"
Autoscale key=".0:$.3"
</label> >
<input Manual
aria-label="Manual database throughput" </span>
aria-required={true}
checked={false}
className="throughputInputRadioBtn"
id="Manual-input"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<label
className="throughputInputRadioBtnLabel"
htmlFor="Manual-input"
>
Manual
</label>
</div>
</div> </div>
</Stack> </Stack>
<Stack <Stack
@@ -702,25 +697,23 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="ms-Stack throughputInputSpacing css-59" className="ms-Stack throughputInputSpacing css-59"
> >
<Text <Text
aria-label="capacity calculator of azure cosmos db" aria-label="ruDescription"
key=".0:$.0" key=".0:$.0"
variant="small" variant="small"
> >
<span <span
aria-label="capacity calculator of azure cosmos db" aria-label="ruDescription"
className="css-54" className="css-54"
> >
Estimate your required RU/s with Estimate your required RU/s with
<StyledLinkBase <StyledLinkBase
aria-label="capacity calculator of azure cosmos db" aria-label="ruDescription"
className="capacitycalculator-link"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
target="_blank" target="_blank"
> >
<LinkBase <LinkBase
aria-label="capacity calculator of azure cosmos db" aria-label="ruDescription"
className="capacitycalculator-link"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
styles={[Function]} styles={[Function]}
target="_blank" target="_blank"
@@ -999,8 +992,8 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
} }
> >
<a <a
aria-label="capacity calculator of azure cosmos db" aria-label="ruDescription"
className="ms-Link capacitycalculator-link root-60" className="ms-Link root-60"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
onClick={[Function]} onClick={[Function]}
target="_blank" target="_blank"
@@ -1338,13 +1331,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
onMouseLeave={[Function]} onMouseLeave={[Function]}
> >
<StyledIconBase <StyledIconBase
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage." ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
> >
<IconBase <IconBase
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage." ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
styles={[Function]} styles={[Function]}
@@ -1624,7 +1617,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
} }
> >
<i <i
aria-label="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage." aria-label="Info"
className="panelInfoIcon root-57" className="panelInfoIcon root-57"
data-icon-name="Info" data-icon-name="Info"
role="img" role="img"
@@ -1642,7 +1635,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</div> </div>
</Stack> </Stack>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Container max RU/s" aria-label="Max request units per second"
errorMessage="" errorMessage=""
id="autoscaleRUValueField" id="autoscaleRUValueField"
key=".0:$.2" key=".0:$.2"
@@ -1665,7 +1658,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
value="4000" value="4000"
> >
<TextFieldBase <TextFieldBase
ariaLabel="Container max RU/s" aria-label="Max request units per second"
deferredValidationTime={200} deferredValidationTime={200}
errorMessage="" errorMessage=""
id="autoscaleRUValueField" id="autoscaleRUValueField"
@@ -1963,7 +1956,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Container max RU/s"
className="ms-TextField-field field-64" className="ms-TextField-field field-64"
id="autoscaleRUValueField" id="autoscaleRUValueField"
min={1000} min={1000}

View File

@@ -195,7 +195,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
</div> </div>
{node.children && ( {node.children && (
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}> <AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
<div className="nodeChildren" data-test={node.label} role="group"> <div className="nodeChildren" data-test={node.label}>
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => ( {TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
<TreeNodeComponent <TreeNodeComponent
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`} key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}

View File

@@ -100,7 +100,6 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group"
> >
<TreeNodeComponent <TreeNodeComponent
generation={3} generation={3}
@@ -252,7 +251,6 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group"
> >
<TreeNodeComponent <TreeNodeComponent
generation={13} generation={13}
@@ -354,7 +352,6 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group"
/> />
</AnimateHeight> </AnimateHeight>
</div> </div>
@@ -468,7 +465,6 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group"
> >
<TreeNodeComponent <TreeNodeComponent
generation={13} generation={13}
@@ -598,7 +594,6 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group"
> >
<TreeNodeComponent <TreeNodeComponent
generation={3} generation={3}

View File

@@ -3,7 +3,7 @@
.treeComponent { .treeComponent {
.nodeItem { .nodeItem {
&:focus { &:focus {
outline: 2px @AccentMedium; outline: 1px dashed @AccentMedium;
} }
.treeNodeHeader { .treeNodeHeader {

View File

@@ -133,6 +133,7 @@ describe("ContainerSampleGenerator", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
// Rejects with error that contains experience
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience); expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
}); });

View File

@@ -577,7 +577,7 @@ export default class Explorer {
try { try {
await Promise.all( await Promise.all(
databasesToLoad.map(async (database: ViewModels.Database) => { databasesToLoad.map(async (database: ViewModels.Database) => {
await database.loadCollections(true); await database.loadCollections();
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id()); const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
if (isNewDatabase) { if (isNewDatabase) {
database.expandDatabase(); database.expandDatabase();

View File

@@ -729,7 +729,6 @@ export class D3ForceGraph implements GraphRenderer {
var iconGroup = newNodes var iconGroup = newNodes
.append("g") .append("g")
.attr("class", "iconContainer") .attr("class", "iconContainer")
.attr("role", "group")
.attr("tabindex", 0) .attr("tabindex", 0)
.attr("aria-label", (d: D3Node) => { .attr("aria-label", (d: D3Node) => {
return this.retrieveNodeCaption(d); return this.retrieveNodeCaption(d);

View File

@@ -40,7 +40,6 @@ export class GraphVizComponent extends React.Component<GraphVizComponentProps> {
{/* svg load more icon inlined as-is here: remove the style="fill:#374649;" so we can override it */} {/* svg load more icon inlined as-is here: remove the style="fill:#374649;" so we can override it */}
<svg <svg
role="img" role="img"
aria-label="graph"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink" xmlnsXlink="http://www.w3.org/1999/xlink"
@@ -136,7 +135,6 @@ export class GraphVizComponent extends React.Component<GraphVizComponentProps> {
<g id="triangleRight"> <g id="triangleRight">
<svg <svg
role="img" role="img"
aria-label="graph"
version="1.1" version="1.1"
id="Layer_1" id="Layer_1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@@ -17,21 +17,19 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
<div className="middlePane"> <div className="middlePane">
<div className="graphTitle"> <div className="graphTitle">
<span className="paneTitle">Graph</span> <span className="paneTitle">Graph</span>
<button <span
style={{ border: "none", background: "none" }}
className="graphExpandCollapseBtn pull-right" className="graphExpandCollapseBtn pull-right"
onClick={this.props.toggleExpandGraph} onClick={this.props.toggleExpandGraph}
role="button" role="button"
aria-expanded={this.props.isTabsContentExpanded} aria-expanded={this.props.isTabsContentExpanded}
aria-label={ aria-name="View graph in full screen"
this.props.isTabsContentExpanded ? "Collapse graph to minimized view" : "View graph in full screen" tabIndex={0}
}
> >
<img <img
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon} src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"} alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
/> />
</button> </span>
</div> </div>
<div className="maingraphContainer"> <div className="maingraphContainer">
<GraphVizComponent forceGraphParams={this.props.graphVizProps.forceGraphParams} /> <GraphVizComponent forceGraphParams={this.props.graphVizProps.forceGraphParams} />

View File

@@ -107,11 +107,11 @@ describe("New Vertex Component", () => {
it("should call onTypeChange method on type dropdown change", () => { it("should call onTypeChange method on type dropdown change", () => {
const DOWN_ARROW = { keyCode: 40 }; const DOWN_ARROW = { keyCode: 40 };
const onTypeChange = jest.fn(); const onTypeChange = jest.fn();
const dropdown = screen.getByRole("combobox"); const dropdown = screen.getByRole("listbox");
dropdown.onclick = onTypeChange(); dropdown.onclick = onTypeChange();
dropdown.onkeydown = onTypeChange(); dropdown.onkeydown = onTypeChange();
fireEvent.keyDown(screen.getByRole("combobox"), DOWN_ARROW); fireEvent.keyDown(screen.getByRole("listbox"), DOWN_ARROW);
fireEvent.click(screen.getByText(/number/)); fireEvent.click(screen.getByText(/number/));
expect(onTypeChange).toHaveBeenCalled(); expect(onTypeChange).toHaveBeenCalled();
}); });

View File

@@ -145,7 +145,6 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
type="text" type="text"
id="propertyKeyNewVertexPane" id="propertyKeyNewVertexPane"
componentRef={input} componentRef={input}
aria-required="true"
placeholder="Key" placeholder="Key"
autoComplete="off" autoComplete="off"
aria-label={`Enter value for propery ${index + 1}`} aria-label={`Enter value for propery ${index + 1}`}
@@ -153,8 +152,6 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)} onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
/> />
</div> </div>
<span className="mandatoryStar">*&nbsp;</span>
<div className="valueCol"> <div className="valueCol">
<TextField <TextField
className="edgeInput" className="edgeInput"
@@ -168,7 +165,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
</div> </div>
<div> <div>
<Dropdown <Dropdown
role="combobox" role="listbox"
placeholder="Select an option" placeholder="Select an option"
defaultSelectedKey={data.values[0].type} defaultSelectedKey={data.values[0].type}
style={{ width: 100 }} style={{ width: 100 }}

View File

@@ -25,7 +25,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
capabilities: [{ name: "EnableMongo" }], capabilities: [{ name: "EnableTable" }],
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
@@ -38,38 +38,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
); );
expect(enableAzureSynapseLinkBtn).toBeDefined(); expect(enableAzureSynapseLinkBtn).toBeDefined();
}); });
it("Button should not be visible for Tables API", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableTable" }],
},
} as DatabaseAccount,
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
expect(enableAzureSynapseLinkBtn).toBeUndefined();
});
it("Button should not be visible for Cassandra API", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableCassandra" }],
},
} as DatabaseAccount,
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
expect(enableAzureSynapseLinkBtn).toBeUndefined();
});
}); });
describe("Enable notebook button", () => { describe("Enable notebook button", () => {

View File

@@ -51,13 +51,11 @@ export function createStaticCommandBarButtons(
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
buttons.push(newCollectionBtn); buttons.push(newCollectionBtn);
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) { const addSynapseLink = createOpenSynapseLinkDialogButton(container);
buttons.push(createDivider()); if (addSynapseLink) {
buttons.push(addSynapseLink); buttons.push(createDivider());
} buttons.push(addSynapseLink);
} }
if (userContext.apiType !== "Tables") { if (userContext.apiType !== "Tables") {
@@ -168,11 +166,7 @@ export function createContextCommandBarButtons(
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { selectedCollection && selectedCollection.onNewMongoShellClick();
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
selectedCollection && selectedCollection.onNewMongoShellClick();
}
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
@@ -204,7 +198,6 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
if (showOpenFullScreen) { if (showOpenFullScreen) {
const label = "Open Full Screen"; const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = { const fullScreenButton: CommandButtonComponentProps = {
id: "openFullScreenBtn",
iconSrc: OpenInTabIcon, iconSrc: OpenInTabIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {

View File

@@ -147,9 +147,12 @@ export class NotificationConsoleComponent extends React.Component<
<div className="notificationConsoleControls"> <div className="notificationConsoleControls">
<Dropdown <Dropdown
label="Filter:" label="Filter:"
role="combobox"
selectedKey={this.state.selectedFilter} selectedKey={this.state.selectedFilter}
options={NotificationConsoleComponent.FilterOptions} options={NotificationConsoleComponent.FilterOptions}
onChange={this.onFilterSelected.bind(this)} onChange={this.onFilterSelected.bind(this)}
aria-labelledby="consoleFilterLabel"
aria-label={this.state.selectedFilter}
/> />
<span className="consoleSplitter" /> <span className="consoleSplitter" />
<span <span

View File

@@ -112,6 +112,8 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
className="notificationConsoleControls" className="notificationConsoleControls"
> >
<Dropdown <Dropdown
aria-label="All"
aria-labelledby="consoleFilterLabel"
label="Filter:" label="Filter:"
onChange={[Function]} onChange={[Function]}
options={ options={
@@ -134,6 +136,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
}, },
] ]
} }
role="combobox"
selectedKey="All" selectedKey="All"
/> />
<span <span
@@ -275,6 +278,8 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
className="notificationConsoleControls" className="notificationConsoleControls"
> >
<Dropdown <Dropdown
aria-label="All"
aria-labelledby="consoleFilterLabel"
label="Filter:" label="Filter:"
onChange={[Function]} onChange={[Function]}
options={ options={
@@ -297,6 +302,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
}, },
] ]
} }
role="combobox"
selectedKey="All" selectedKey="All"
/> />
<span <span

View File

@@ -1,23 +0,0 @@
import * as InMemoryContentProviderUtils from "./ContentProviders/InMemoryContentProviderUtils";
describe("fromContentUri", () => {
it("fromContentUri should return valid result", () => {
const contentUri = "memory://resource/path";
const result = "resource";
expect(InMemoryContentProviderUtils.fromContentUri(contentUri)).toEqual(result);
});
it("fromContentUri should return undefined on invalid input", () => {
const contentUri = "invalid";
expect(InMemoryContentProviderUtils.fromContentUri(contentUri)).toEqual(undefined);
});
it("toContentUri should return valid result", () => {
const path = "resource/path";
const result = "memory://resource/path";
expect(InMemoryContentProviderUtils.toContentUri(path)).toEqual(result);
});
});

View File

@@ -89,10 +89,9 @@ export interface AddCollectionPanelState {
enableIndexing: boolean; enableIndexing: boolean;
isSharded: boolean; isSharded: boolean;
partitionKey: string; partitionKey: string;
subPartitionKeys: string[];
enableDedicatedThroughput: boolean; enableDedicatedThroughput: boolean;
createMongoWildCardIndex: boolean; createMongoWildCardIndex: boolean;
useHashV1: boolean; useHashV2: boolean;
enableAnalyticalStore: boolean; enableAnalyticalStore: boolean;
uniqueKeys: string[]; uniqueKeys: string[];
errorMessage: string; errorMessage: string;
@@ -122,11 +121,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
enableIndexing: true, enableIndexing: true,
isSharded: userContext.apiType !== "Tables", isSharded: userContext.apiType !== "Tables",
partitionKey: this.getPartitionKey(), partitionKey: this.getPartitionKey(),
subPartitionKeys: [],
enableDedicatedThroughput: false, enableDedicatedThroughput: false,
createMongoWildCardIndex: createMongoWildCardIndex:
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"), isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
useHashV1: false, useHashV2: false,
enableAnalyticalStore: false, enableAnalyticalStore: false,
uniqueKeys: [], uniqueKeys: [],
errorMessage: "", errorMessage: "",
@@ -262,46 +260,37 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
true true
).toLocaleLowerCase()}.`} ).toLocaleLowerCase()}.`}
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
true
).toLocaleLowerCase()}.`}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<div role="radiogroup"> <input
<input className="panelRadioBtn"
className="panelRadioBtn" checked={this.state.createNewDatabase}
checked={this.state.createNewDatabase} aria-label="Create new database"
aria-label="Create new database" aria-checked={this.state.createNewDatabase}
aria-checked={this.state.createNewDatabase} name="databaseType"
name="databaseType" type="radio"
type="radio" role="radio"
role="radio" id="databaseCreateNew"
id="databaseCreateNew" tabIndex={0}
tabIndex={0} onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)} />
/> <span className="panelRadioBtnLabel">Create new</span>
<span className="panelRadioBtnLabel">Create new</span>
<input <input
className="panelRadioBtn" className="panelRadioBtn"
checked={!this.state.createNewDatabase} checked={!this.state.createNewDatabase}
aria-label="Use existing database" aria-label="Use existing database"
aria-checked={!this.state.createNewDatabase} aria-checked={!this.state.createNewDatabase}
name="databaseType" name="databaseType"
type="radio" type="radio"
role="radio" role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)} onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/> />
<span className="panelRadioBtnLabel">Use existing</span> <span className="panelRadioBtnLabel">Use existing</span>
</div>
</Stack> </Stack>
{this.state.createNewDatabase && ( {this.state.createNewDatabase && (
@@ -347,14 +336,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
true true
).toLocaleLowerCase()} within the database.`} ).toLocaleLowerCase()} within the database.`}
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
true
).toLocaleLowerCase()} within the database.`}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
)} )}
@@ -402,13 +384,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`} content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
role="button"
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -491,14 +467,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data." "Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
} }
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -545,12 +514,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getPartitionKeyTooltipText()} content={this.getPartitionKeyTooltipText()}
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={this.getPartitionKeyTooltipText()}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -582,77 +546,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
}} }}
/> />
{userContext.apiType === "SQL" &&
this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => {
return (
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${index}`} horizontal>
<div
style={{
width: "20px",
border: "solid",
borderWidth: "0px 0px 1px 1px",
marginRight: "5px",
}}
></div>
<input
type="text"
id="addCollection-partitionKeyValue"
key={`addCollection-partitionKeyValue_${index}`}
aria-required
required
size={40}
tabIndex={index > 0 ? 1 : 0}
className="panelTextField"
autoComplete="off"
placeholder={this.getPartitionKeyPlaceHolder(index)}
aria-label={this.getPartitionKeyName()}
pattern={".*"}
title={""}
value={subPartitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const subPartitionKeys = [...this.state.subPartitionKeys];
if (!this.state.subPartitionKeys[index] && !event.target.value.startsWith("/")) {
subPartitionKeys[index] = "/" + event.target.value.trim();
this.setState({ subPartitionKeys });
} else {
subPartitionKeys[index] = event.target.value.trim();
this.setState({ subPartitionKeys });
}
}}
/>
<IconButton
iconProps={{ iconName: "Delete" }}
style={{ height: 27 }}
onClick={() => {
const subPartitionKeys = this.state.subPartitionKeys.filter((uniqueKey, j) => index !== j);
this.setState({ subPartitionKeys });
}}
/>
</Stack>
);
})}
{userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing">
<DefaultButton
styles={{ root: { padding: 0, width: 250, height: 30 }, label: { fontSize: 12 } }}
hidden={this.state.useHashV1}
disabled={this.state.subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
onClick={() => this.setState({ subPartitionKeys: [...this.state.subPartitionKeys, ""] })}
>
Add hierarchical partition key (preview)
</DefaultButton>
{this.state.subPartitionKeys.length > 0 && (
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to
partition your data with up to three levels of keys for better data distribution. Requires preview
version of .NET V3 or Java V4 SDK.{" "}
<Link href="https://aka.ms/cosmos-hierarchical-partitioning" target="_blank">
Learn more
</Link>
</Text>
)}
</Stack>
)}
</Stack> </Stack>
)} )}
@@ -679,17 +572,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
does not count towards the throughput you provisioned for the database. This throughput amount will be does not count towards the throughput you provisioned for the database. This throughput amount will be
billed in addition to the throughput amount you provisioned at the database level.`} billed in addition to the throughput amount you provisioned at the database level.`}
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`You can optionally provision dedicated throughput for a ${getCollectionName().toLocaleLowerCase()} within a database that has throughput
provisioned. This dedicated throughput amount will not be shared with other ${getCollectionName(
true
).toLocaleLowerCase()} in the database and
does not count towards the throughput you provisioned for the database. This throughput amount will be
billed in addition to the throughput amount you provisioned at the database level.`}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
)} )}
@@ -720,18 +603,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Text> </Text>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={ content="Unique keys provide developers with the ability to add a layer of data integrity to their database. By
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key." creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
} per partition key."
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."
}
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -794,48 +670,40 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getAnalyticalStorageTooltipContent()} content={this.getAnalyticalStorageTooltipContent()}
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without
impacting the performance of transactional workloads."
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<div role="radiogroup"> <input
<input className="panelRadioBtn"
className="panelRadioBtn" checked={this.state.enableAnalyticalStore}
checked={this.state.enableAnalyticalStore} disabled={!this.isSynapseLinkEnabled()}
disabled={!this.isSynapseLinkEnabled()} aria-label="Enable analytical store"
aria-label="Enable analytical store" aria-checked={this.state.enableAnalyticalStore}
aria-checked={this.state.enableAnalyticalStore} name="analyticalStore"
name="analyticalStore" type="radio"
type="radio" role="radio"
role="radio" id="enableAnalyticalStoreBtn"
id="enableAnalyticalStoreBtn" tabIndex={0}
tabIndex={0} onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)} />
/> <span className="panelRadioBtnLabel">On</span>
<span className="panelRadioBtnLabel">On</span>
<input <input
className="panelRadioBtn" className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore} checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()} disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store" aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore} aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore" name="analyticalStore"
type="radio" type="radio"
role="radio" role="radio"
id="disableAnalyticalStoreBtn" id="disableAnalyticalStoreBtn"
tabIndex={0} tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)} onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/> />
<span className="panelRadioBtnLabel">Off</span> <span className="panelRadioBtnLabel">Off</span>
</div>
</Stack> </Stack>
{!this.isSynapseLinkEnabled() && ( {!this.isSynapseLinkEnabled() && (
@@ -879,12 +747,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development." content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
> >
<Icon <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
/>
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -904,29 +767,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
{userContext.apiType === "SQL" && ( {userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing"> <Checkbox
<Checkbox label="My partition key is larger than 101 bytes"
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)" checked={this.state.useHashV2}
checked={this.state.useHashV1} styles={{
styles={{ text: { fontSize: 12 },
text: { fontSize: 12 }, checkbox: { width: 12, height: 12 },
checkbox: { width: 12, height: 12 }, label: { padding: 0, alignItems: "center" },
label: { padding: 0, alignItems: "center", wordWrap: "break-word", whiteSpace: "break-spaces" }, }}
}} onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => this.setState({ useHashV2: isChecked })
this.setState({ useHashV1: isChecked, subPartitionKeys: [] }) }
} />
/>
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> To ensure compatibility with
older SDKs, the created container will use a legacy partitioning scheme that supports partition
key values of size only up to 101 bytes. If this is enabled, you will not be able to use
hierarchical partition keys.{" "}
<Link href="https://aka.ms/cosmos-large-pk" target="_blank">
Learn more
</Link>
</Text>
</Stack>
)} )}
</Stack> </Stack>
</CollapsibleSectionComponent> </CollapsibleSectionComponent>
@@ -981,20 +833,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName; return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
} }
private getPartitionKeyPlaceHolder(index?: number): string { private getPartitionKeyPlaceHolder(): string {
switch (userContext.apiType) { switch (userContext.apiType) {
case "Mongo": case "Mongo":
return "e.g., categoryId"; return "e.g., address.zipCode";
case "Gremlin": case "Gremlin":
return "e.g., /address"; return "e.g., /address";
case "SQL":
return `${
index === undefined
? "Required - first partition key e.g., /TenantId"
: index === 0
? "second partition key e.g., /UserId"
: "third partition key e.g., /SessionId"
}`;
default: default:
return "e.g., /address/zipCode"; return "e.g., /address/zipCode";
} }
@@ -1116,7 +960,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return userContext.apiType === "SQL" ? "/pk" : "pk"; return userContext.apiType === "SQL" ? "/pk" : "pk";
} }
if (this.props.isQuickstart) { if (this.props.isQuickstart) {
return userContext.apiType === "SQL" ? "/categoryId" : "categoryId"; return userContext.apiType === "SQL" ? "/address" : "address";
} }
return ""; return "";
} }
@@ -1320,16 +1164,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys(); const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys();
const partitionKeyVersion = this.state.useHashV1 ? undefined : 2; const partitionKeyVersion = this.state.useHashV2 ? 2 : undefined;
const partitionKey: DataModels.PartitionKey = partitionKeyString const partitionKey: DataModels.PartitionKey = partitionKeyString
? { ? {
paths: [ paths: [partitionKeyString],
partitionKeyString, kind: "Hash",
...(userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0
? this.state.subPartitionKeys
: []),
],
kind: userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0 ? "MultiHash" : "Hash",
version: partitionKeyVersion, version: partitionKeyVersion,
} }
: undefined; : undefined;

View File

@@ -89,8 +89,8 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
} }
} catch (error) { } catch (error) {
setLoadingFalse(); setLoadingFalse();
setFormError(error);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
setFormError(errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.DeleteDatabase, Action.DeleteDatabase,
{ {

View File

@@ -48,7 +48,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
)} )}
</Text> </Text>
{showErrorDetails && ( {showErrorDetails && (
<a className="paneErrorLink" role="button" onClick={expandConsole} tabIndex={0} onKeyPress={expandConsole}> <a className="paneErrorLink" role="link" onClick={expandConsole}>
More details More details
</a> </a>
)} )}

View File

@@ -21,11 +21,6 @@ export const SettingsPane: FunctionComponent = () => {
const [customItemPerPage, setCustomItemPerPage] = useState<number>( const [customItemPerPage, setCustomItemPerPage] = useState<number>(
LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0 LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0
); );
const [containerPaginationEnabled, setContainerPaginationEnabled] = useState<boolean>(
LocalStorageUtility.hasItem(StorageKey.ContainerPaginationEnabled)
? LocalStorageUtility.getEntryString(StorageKey.ContainerPaginationEnabled) === "true"
: false
);
const [crossPartitionQueryEnabled, setCrossPartitionQueryEnabled] = useState<boolean>( const [crossPartitionQueryEnabled, setCrossPartitionQueryEnabled] = useState<boolean>(
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled)
? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true" ? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
@@ -55,7 +50,6 @@ export const SettingsPane: FunctionComponent = () => {
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage
); );
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
@@ -191,25 +185,6 @@ export const SettingsPane: FunctionComponent = () => {
</div> </div>
</div> </div>
)} )}
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">
Enable container pagination
<InfoTooltip>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable container pagination"
checked={containerPaginationEnabled}
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
/>
</div>
</div>
{shouldShowCrossPartitionOption && ( {shouldShowCrossPartitionOption && (
<div className="settingsSection"> <div className="settingsSection">
<div className="settingsSectionPart"> <div className="settingsSectionPart">

View File

@@ -97,35 +97,6 @@ exports[`Settings Pane should render Default properly 1`] = `
</div> </div>
</div> </div>
</div> </div>
<div
className="settingsSection"
>
<div
className="settingsSectionPart"
>
<div
className="settingsSectionLabel"
>
Enable container pagination
<InfoTooltip>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</InfoTooltip>
</div>
<StyledCheckboxBase
ariaLabel="Enable container pagination"
checked={false}
className="padding"
onChange={[Function]}
styles={
Object {
"label": Object {
"padding": 0,
},
}
}
/>
</div>
</div>
<div <div
className="settingsSection" className="settingsSection"
> >
@@ -211,35 +182,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
<div <div
className="paneMainContent" className="paneMainContent"
> >
<div
className="settingsSection"
>
<div
className="settingsSectionPart"
>
<div
className="settingsSectionLabel"
>
Enable container pagination
<InfoTooltip>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</InfoTooltip>
</div>
<StyledCheckboxBase
ariaLabel="Enable container pagination"
checked={false}
className="padding"
onChange={[Function]}
styles={
Object {
"label": Object {
"padding": 0,
},
}
}
/>
</div>
</div>
<div <div
className="settingsSection" className="settingsSection"
> >

View File

@@ -242,11 +242,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
submitButtonText: getButtonLabel(userContext.apiType), submitButtonText: getButtonLabel(userContext.apiType),
onSubmit, onSubmit,
}; };
const handlekeypressaddentity = (event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter" || event.key === "Space") {
addNewEntity();
}
};
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
@@ -289,13 +284,7 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
); );
})} })}
{userContext.apiType !== "Cassandra" && ( {userContext.apiType !== "Cassandra" && (
<Stack <Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
horizontal
onClick={addNewEntity}
className="addButtonEntiy"
tabIndex={0}
onKeyPress={handlekeypressaddentity}
>
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" /> <Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text> <Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
</Stack> </Stack>

View File

@@ -29,14 +29,10 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
className="addButtonEntiy" className="addButtonEntiy"
horizontal={true} horizontal={true}
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]}
tabIndex={0}
> >
<div <div
className="ms-Stack addButtonEntiy css-53" className="ms-Stack addButtonEntiy css-53"
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]}
tabIndex={0}
> >
<StyledImageBase <StyledImageBase
alt="Add Entity" alt="Add Entity"

View File

@@ -31,7 +31,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
directionalHint={4} directionalHint={4}
> >
<Icon <Icon
ariaLabel="A database is analogous to a namespace. It is the unit of management for a set of containers."
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
@@ -42,43 +41,39 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
horizontal={true} horizontal={true}
verticalAlign="center" verticalAlign="center"
> >
<div <input
role="radiogroup" aria-checked={true}
aria-label="Create new database"
checked={true}
className="panelRadioBtn"
id="databaseCreateNew"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
> >
<input Create new
aria-checked={true} </span>
aria-label="Create new database" <input
checked={true} aria-checked={false}
className="panelRadioBtn" aria-label="Use existing database"
id="databaseCreateNew" checked={false}
name="databaseType" className="panelRadioBtn"
onChange={[Function]} name="databaseType"
role="radio" onChange={[Function]}
tabIndex={0} role="radio"
type="radio" tabIndex={0}
/> type="radio"
<span />
className="panelRadioBtnLabel" <span
> className="panelRadioBtnLabel"
Create new >
</span> Use existing
<input </span>
aria-checked={false}
aria-label="Use existing database"
checked={false}
className="panelRadioBtn"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Use existing
</span>
</div>
</Stack> </Stack>
<Stack <Stack
className="panelGroupSpacing" className="panelGroupSpacing"
@@ -129,7 +124,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
directionalHint={4} directionalHint={4}
> >
<Icon <Icon
ariaLabel="Throughput configured at the database level will be shared across all containers within the database."
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
@@ -169,10 +163,8 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
directionalHint={4} directionalHint={4}
> >
<Icon <Icon
ariaLabel="Unique identifier for the container and used for id-based routing through REST and all SDKs."
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
role="button"
tabIndex={0} tabIndex={0}
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
@@ -214,7 +206,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
directionalHint={4} directionalHint={4}
> >
<Icon <Icon
ariaLabel="The partition key is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume. For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice."
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
@@ -232,36 +223,13 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
id="addCollection-partitionKeyValue" id="addCollection-partitionKeyValue"
onChange={[Function]} onChange={[Function]}
pattern=".*" pattern=".*"
placeholder="Required - first partition key e.g., /TenantId" placeholder="e.g., /address/zipCode"
required={true} required={true}
size={40} size={40}
title="" title=""
type="text" type="text"
value="" value=""
/> />
<Stack
className="panelGroupSpacing"
>
<CustomizedDefaultButton
disabled={false}
hidden={false}
onClick={[Function]}
styles={
Object {
"label": Object {
"fontSize": 12,
},
"root": Object {
"height": 30,
"padding": 0,
"width": 250,
},
}
}
>
Add hierarchical partition key (preview)
</CustomizedDefaultButton>
</Stack>
</Stack> </Stack>
<Stack> <Stack>
<Stack <Stack
@@ -278,7 +246,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
directionalHint={4} directionalHint={4}
> >
<Icon <Icon
ariaLabel="Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
@@ -336,7 +303,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
directionalHint={4} directionalHint={4}
> >
<Icon <Icon
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads."
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0} tabIndex={0}
@@ -347,46 +313,42 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
horizontal={true} horizontal={true}
verticalAlign="center" verticalAlign="center"
> >
<div <input
role="radiogroup" aria-checked={false}
aria-label="Enable analytical store"
checked={false}
className="panelRadioBtn"
disabled={true}
id="enableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
> >
<input On
aria-checked={false} </span>
aria-label="Enable analytical store" <input
checked={false} aria-checked={true}
className="panelRadioBtn" aria-label="Disable analytical store"
disabled={true} checked={true}
id="enableAnalyticalStoreBtn" className="panelRadioBtn"
name="analyticalStore" disabled={true}
onChange={[Function]} id="disableAnalyticalStoreBtn"
role="radio" name="analyticalStore"
tabIndex={0} onChange={[Function]}
type="radio" role="radio"
/> tabIndex={0}
<span type="radio"
className="panelRadioBtnLabel" />
> <span
On className="panelRadioBtnLabel"
</span> >
<input Off
aria-checked={true} </span>
aria-label="Disable analytical store"
checked={true}
className="panelRadioBtn"
disabled={true}
id="disableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Off
</span>
</div>
</Stack> </Stack>
<Stack <Stack
className="panelGroupSpacing" className="panelGroupSpacing"
@@ -434,49 +396,26 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
className="panelGroupSpacing" className="panelGroupSpacing"
id="collapsibleSectionContent" id="collapsibleSectionContent"
> >
<Stack <StyledCheckboxBase
className="panelGroupSpacing" checked={false}
> label="My partition key is larger than 101 bytes"
<StyledCheckboxBase onChange={[Function]}
checked={false} styles={
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)" Object {
onChange={[Function]} "checkbox": Object {
styles={ "height": 12,
Object { "width": 12,
"checkbox": Object { },
"height": 12, "label": Object {
"width": 12, "alignItems": "center",
}, "padding": 0,
"label": Object { },
"alignItems": "center", "text": Object {
"padding": 0, "fontSize": 12,
"whiteSpace": "break-spaces", },
"wordWrap": "break-word",
},
"text": Object {
"fontSize": 12,
},
}
} }
/> }
<Text />
variant="small"
>
<Icon
className="removeIcon"
iconName="InfoSolid"
tabIndex={0}
/>
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.
<StyledLinkBase
href="https://aka.ms/cosmos-large-pk"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
</Stack>
</Stack> </Stack>
</CollapsibleSectionComponent> </CollapsibleSectionComponent>
</div> </div>

View File

@@ -1,4 +1,4 @@
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react"; import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble"; import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react"; import React from "react";
@@ -18,11 +18,6 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
return <></>; return <></>;
} }
let totalSteps = 9;
if (userContext.isTryCosmosDBSubscription) {
totalSteps = 10;
}
switch (step) { switch (step) {
case 1: case 1:
return isSampleDBExpanded ? ( return isSampleDBExpanded ? (
@@ -38,7 +33,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
}, },
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 1 of " + totalSteps} footerContent="Step 1 of 8"
> >
Start viewing and working with your data by opening Documents under Data Start viewing and working with your data by opening Documents under Data
</TeachingBubble> </TeachingBubble>
@@ -60,7 +55,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(1), onClick: () => setStep(1),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 2 of " + totalSteps} footerContent="Step 2 of 8"
> >
View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do
so. so.
@@ -83,7 +78,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(2), onClick: () => setStep(2),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 3 of " + totalSteps} footerContent="Step 3 of 8"
> >
Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and
drivers to do so. drivers to do so.
@@ -104,7 +99,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(3), onClick: () => setStep(3),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 4 of " + totalSteps} footerContent="Step 4 of 8"
> >
Query your data using the filter function. Azure Cosmos DB for MongoDB provides comprehensive support for Query your data using the filter function. Azure Cosmos DB for MongoDB provides comprehensive support for
MongoDB query language constructs. You can also use your favorite MongoDB tools and drivers to do so. MongoDB query language constructs. You can also use your favorite MongoDB tools and drivers to do so.
@@ -125,7 +120,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(4), onClick: () => setStep(4),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 5 of " + totalSteps} footerContent="Step 5 of 8"
> >
Change throughput provisioned to your collection according to your needs Change throughput provisioned to your collection according to your needs
</TeachingBubble> </TeachingBubble>
@@ -145,7 +140,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(5), onClick: () => setStep(5),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 6 of " + totalSteps} footerContent="Step 6 of 8"
> >
Use the indexing policy editor to create and edit your indexes. Use the indexing policy editor to create and edit your indexes.
</TeachingBubble> </TeachingBubble>
@@ -165,54 +160,12 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(6), onClick: () => setStep(6),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 7 of " + totalSteps} footerContent="Step 7 of 8"
> >
Visualize your data, store queries in an interactive document Visualize your data, store queries in an interactive document
</TeachingBubble> </TeachingBubble>
); );
case 8: case 8:
return (
<TeachingBubble
headline="Launch full screen"
target={"#openFullScreenBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(10)),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(7),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 8 of " + totalSteps}
>
This will open a new tab in your browser to use Cosmos DB Explorer. Using the provided URLs you can share
read-write or read-only access with other people.
</TeachingBubble>
);
case 9:
return (
<TeachingBubble
headline="Boost your experience"
target={"#freeTierTeachingBubble"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(10),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(8),
}}
calloutProps={{ directionalHint: DirectionalHint.leftCenter }}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 9 of " + totalSteps}
>
Unlock everything Azure Cosmos DB has to offer When you&apos;re ready, upgrade to production.
</TeachingBubble>
);
case 10:
return ( return (
<TeachingBubble <TeachingBubble
headline="Congratulations!" headline="Congratulations!"
@@ -227,10 +180,10 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
}} }}
secondaryButtonProps={{ secondaryButtonProps={{
text: "Previous", text: "Previous",
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(8)), onClick: () => setStep(7),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step " + totalSteps + " of " + totalSteps} footerContent="Step 8 of 8"
> >
<Stack> <Stack>
<Text style={{ color: "white" }}> <Text style={{ color: "white" }}>

View File

@@ -1,4 +1,4 @@
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react"; import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble"; import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react"; import React from "react";
@@ -17,10 +17,6 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
if (userContext.apiType !== "SQL") { if (userContext.apiType !== "SQL") {
return <></>; return <></>;
} }
let totalSteps = 8;
if (userContext.isTryCosmosDBSubscription) {
totalSteps = 9;
}
switch (step) { switch (step) {
case 1: case 1:
@@ -37,7 +33,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
}, },
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 1 of " + totalSteps} footerContent="Step 1 of 7"
> >
Start viewing and working with your data by opening Items under Data Start viewing and working with your data by opening Items under Data
</TeachingBubble> </TeachingBubble>
@@ -59,7 +55,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(1), onClick: () => setStep(1),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 2 of " + totalSteps} footerContent="Step 2 of 7"
> >
View item here using the items window. Additionally you can also filter items to be reviewed with the filter View item here using the items window. Additionally you can also filter items to be reviewed with the filter
function function
@@ -82,7 +78,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(2), onClick: () => setStep(2),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 3 of " + totalSteps} footerContent="Step 3 of 7"
> >
Add new item by copy / pasting JSON; or uploading a JSON Add new item by copy / pasting JSON; or uploading a JSON
</TeachingBubble> </TeachingBubble>
@@ -102,7 +98,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(3), onClick: () => setStep(3),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 4 of " + totalSteps} footerContent="Step 4 of 7"
> >
Query your data using either the filter function or new query. Query your data using either the filter function or new query.
</TeachingBubble> </TeachingBubble>
@@ -122,7 +118,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(4), onClick: () => setStep(4),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 5 of " + totalSteps} footerContent="Step 5 of 7"
> >
Change throughput provisioned to your container according to your needs Change throughput provisioned to your container according to your needs
</TeachingBubble> </TeachingBubble>
@@ -142,54 +138,12 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(5), onClick: () => setStep(5),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 6 of " + totalSteps} footerContent="Step 6 of 7"
> >
Visualize your data, store queries in an interactive document Visualize your data, store queries in an interactive document
</TeachingBubble> </TeachingBubble>
); );
case 7: case 7:
return (
<TeachingBubble
headline="Launch full screen"
target={"#openFullScreenBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(9)),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(6),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 7 of " + totalSteps}
>
This will open a new tab in your browser to use Cosmos DB Explorer. Using the provided URLs you can share
read-write or read-only access with other people.
</TeachingBubble>
);
case 8:
return (
<TeachingBubble
headline="Boost your experience"
target={"#freeTierTeachingBubble"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(9),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(7),
}}
calloutProps={{ directionalHint: DirectionalHint.leftCenter }}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 8 of " + totalSteps}
>
Unlock everything Azure Cosmos DB has to offer When you&apos;re ready, upgrade to production.
</TeachingBubble>
);
case 9:
return ( return (
<TeachingBubble <TeachingBubble
headline="Congratulations!" headline="Congratulations!"
@@ -204,10 +158,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
}} }}
secondaryButtonProps={{ secondaryButtonProps={{
text: "Previous", text: "Previous",
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(7)), onClick: () => setStep(6),
}} }}
onDismiss={() => onDimissTeachingBubble()} onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step " + totalSteps + " of " + totalSteps} footerContent="Step 7 of 7"
> >
<Stack> <Stack>
<Text style={{ color: "white" }}> <Text style={{ color: "white" }}>

View File

@@ -116,14 +116,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<form className="connectExplorerFormContainer"> <form className="connectExplorerFormContainer">
<div className="splashScreenContainer"> <div className="splashScreenContainer">
<div className="splashScreen"> <div className="splashScreen">
<div <div className="title">
className="title"
aria-label={
userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"
}
>
{userContext.apiType === "Postgres" {userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL" ? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"} : "Welcome to Azure Cosmos DB"}
@@ -535,7 +528,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
> >
{item.title} {item.title}
</Link> </Link>
<Image src={LinkIcon} alt=" " /> <Image src={LinkIcon} />
</Stack> </Stack>
<Text>{item.description}</Text> <Text>{item.description}</Text>
</Stack> </Stack>
@@ -570,17 +563,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
} }
private getLearningResourceItems(): JSX.Element { private getLearningResourceItems(): JSX.Element {
interface item { let items: { link: string; title: string; description: string }[];
link: string;
title: string;
description: string;
}
const cdbLiveTv: item = {
link: "https://developer.azurecosmosdb.com/tv",
title: "Learn the Fundamentals",
description: "Watch Azure Cosmos DB Live TV show introductory and how to videos.",
};
let items: item[];
switch (userContext.apiType) { switch (userContext.apiType) {
case "SQL": case "SQL":
case "Postgres": case "Postgres":
@@ -590,7 +573,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Get Started using an SDK", title: "Get Started using an SDK",
description: "Learn about the Azure Cosmos DB SDK.", description: "Learn about the Azure Cosmos DB SDK.",
}, },
cdbLiveTv, {
link: "https://aka.ms/msl-complex-queries",
title: "Master Complex Queries",
description: "Learn how to author complex queries.",
},
{ {
link: "https://aka.ms/msl-move-data", link: "https://aka.ms/msl-move-data",
title: "Migrate Your Data", title: "Migrate Your Data",
@@ -610,7 +597,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Getting Started Guide", title: "Getting Started Guide",
description: "Learn the basics to get started.", description: "Learn the basics to get started.",
}, },
cdbLiveTv, {
link: "http://aka.ms/mongodotnet",
title: "Build a web API",
description: "Create a web API with the.NET SDK.",
},
]; ];
break; break;
case "Cassandra": case "Cassandra":
@@ -620,7 +611,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Create a Container", title: "Create a Container",
description: "Get to know the create a container options.", description: "Get to know the create a container options.",
}, },
cdbLiveTv, {
link: "https://aka.ms/cassandraserverdiagnostics",
title: "Run Server Diagnostics",
description: "Learn how to run server diagnostics.",
},
{ {
link: "https://aka.ms/Cassandrathroughput", link: "https://aka.ms/Cassandrathroughput",
title: "Provision Throughput", title: "Provision Throughput",
@@ -640,7 +635,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Import Graph Data", title: "Import Graph Data",
description: "Learn Bulk ingestion data using BulkExecutor", description: "Learn Bulk ingestion data using BulkExecutor",
}, },
cdbLiveTv, {
link: "https://aka.ms/graphoptimize",
title: "Optimize your Queries",
description: "Learn how to evaluate your Gremlin queries",
},
]; ];
break; break;
case "Tables": case "Tables":
@@ -655,7 +654,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Build a Java App", title: "Build a Java App",
description: "Create a Azure Cosmos DB for Table app with Java SDK ", description: "Create a Azure Cosmos DB for Table app with Java SDK ",
}, },
cdbLiveTv, {
link: "https://aka.ms/tablenodejs",
title: "Build a Node.js App",
description: "Create a Azure Cosmos DB for Table app with Node.js SDK",
},
]; ];
break; break;
} }

View File

@@ -516,7 +516,7 @@ export default class QueryBuilderViewModel {
}; };
public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => { public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
if (event.key === "Enter" || event.key === "Space") { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.addClauseIndex(this.clauseArray().length - 1); this.addClauseIndex(this.clauseArray().length - 1);
event.stopPropagation(); event.stopPropagation();
return false; return false;

View File

@@ -1,6 +1,6 @@
import React, { Component } from "react"; import React, { Component } from "react";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { configContext } from "../../../ConfigContext"; import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -9,8 +9,6 @@ import { isInvalidParentFrameOrigin, isReadyMessage } from "../../../Utils/Messa
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import TabsBase from "../TabsBase"; import TabsBase from "../TabsBase";
import { getMongoShellOrigin } from "./getMongoShellOrigin";
import { getMongoShellUrl } from "./getMongoShellUrl";
//eslint-disable-next-line //eslint-disable-next-line
class MessageType { class MessageType {
@@ -49,6 +47,7 @@ export default class MongoShellTabComponent extends Component<
IMongoShellTabComponentProps, IMongoShellTabComponentProps,
IMongoShellTabComponentStates IMongoShellTabComponentStates
> { > {
private _runtimeEndpoint: string;
private _logTraces: Map<string, number>; private _logTraces: Map<string, number>;
constructor(props: IMongoShellTabComponentProps) { constructor(props: IMongoShellTabComponentProps) {
@@ -56,7 +55,7 @@ export default class MongoShellTabComponent extends Component<
this._logTraces = new Map(); this._logTraces = new Map();
this.state = { this.state = {
url: getMongoShellUrl(), url: this.getURL(),
}; };
props.onMongoShellTabAccessor({ props.onMongoShellTabAccessor({
@@ -66,6 +65,22 @@ export default class MongoShellTabComponent extends Component<
window.addEventListener("message", this.handleMessage.bind(this), false); window.addEventListener("message", this.handleMessage.bind(this), false);
} }
public getURL(): string {
const { databaseAccount: account } = userContext;
const resourceId = account?.id;
const accountName = account?.name;
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
let baseUrl = "/content/mongoshell/dist/";
if (userContext.portalEnv === "localhost") {
baseUrl = "/content/mongoshell/";
}
return `${extensionEndpoint}${baseUrl}index.html?resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
}
//eslint-disable-next-line //eslint-disable-next-line
public setContentFocus(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {} public setContentFocus(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {}
@@ -121,7 +136,6 @@ export default class MongoShellTabComponent extends Component<
const collectionId = this.props.collection.id(); const collectionId = this.props.collection.id();
const apiEndpoint = configContext.BACKEND_ENDPOINT; const apiEndpoint = configContext.BACKEND_ENDPOINT;
const encryptedAuthToken: string = userContext.accessToken; const encryptedAuthToken: string = userContext.accessToken;
const targetOrigin = getMongoShellOrigin();
shellIframe.contentWindow.postMessage( shellIframe.contentWindow.postMessage(
{ {
@@ -137,7 +151,7 @@ export default class MongoShellTabComponent extends Component<
apiEndpoint: apiEndpoint, apiEndpoint: apiEndpoint,
}, },
}, },
targetOrigin configContext.BACKEND_ENDPOINT
); );
} }

View File

@@ -1,86 +0,0 @@
import { extractFeatures } from "Platform/Hosted/extractFeatures";
import { configContext } from "../../../ConfigContext";
import { updateUserContext } from "../../../UserContext";
import { getMongoShellOrigin } from "./getMongoShellOrigin";
describe("getMongoShellOrigin", () => {
(window as { origin: string }).origin = "window_origin";
beforeEach(() => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV1": "false",
"feature.enableLegacyMongoShellV2": "false",
"feature.enableLegacyMongoShellV1Debug": "false",
"feature.enableLegacyMongoShellV2Debug": "false",
"feature.loadLegacyMongoShellFromBE": "false",
})
),
});
});
it("should return by default", () => {
expect(getMongoShellOrigin()).toBe(window.origin);
});
it("should return window.origin when enableLegacyMongoShellV1", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV1": "true",
})
),
});
expect(getMongoShellOrigin()).toBe(window.origin);
});
it("should return window.origin when enableLegacyMongoShellV2===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV2": "true",
})
),
});
expect(getMongoShellOrigin()).toBe(window.origin);
});
it("should return window.origin when enableLegacyMongoShellV1Debug===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV1Debug": "true",
})
),
});
expect(getMongoShellOrigin()).toBe(window.origin);
});
it("should return window.origin when enableLegacyMongoShellV2Debug===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV2Debug": "true",
})
),
});
expect(getMongoShellOrigin()).toBe(window.origin);
});
it("should return BACKEND_ENDPOINT when loadLegacyMongoShellFromBE===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.loadLegacyMongoShellFromBE": "true",
})
),
});
expect(getMongoShellOrigin()).toBe(configContext.BACKEND_ENDPOINT);
});
});

View File

@@ -1,10 +0,0 @@
import { configContext } from "../../../ConfigContext";
import { userContext } from "../../../UserContext";
export function getMongoShellOrigin(): string {
if (userContext.features.loadLegacyMongoShellFromBE === true) {
return configContext.BACKEND_ENDPOINT;
}
return window.origin;
}

View File

@@ -1,206 +0,0 @@
import { extractFeatures } from "Platform/Hosted/extractFeatures";
import { Platform, configContext, resetConfigContext, updateConfigContext } from "../../../ConfigContext";
import { updateUserContext, userContext } from "../../../UserContext";
import { getExtensionEndpoint, getMongoShellUrl } from "./getMongoShellUrl";
const mongoBackendEndpoint = "https://localhost:1234";
describe("getMongoShellUrl", () => {
let queryString = "";
beforeEach(() => {
resetConfigContext();
updateConfigContext({
BACKEND_ENDPOINT: mongoBackendEndpoint,
platform: Platform.Hosted,
});
updateUserContext({
subscriptionId: "fakeSubscriptionId",
resourceGroup: "fakeResourceGroup",
databaseAccount: {
id: "fakeId",
name: "fakeName",
location: "fakeLocation",
type: "fakeType",
kind: "fakeKind",
properties: {
documentEndpoint: "fakeDocumentEndpoint",
tableEndpoint: "fakeTableEndpoint",
gremlinEndpoint: "fakeGremlinEndpoint",
cassandraEndpoint: "fakeCassandraEndpoint",
},
},
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV1": "false",
"feature.enableLegacyMongoShellV2": "false",
"feature.enableLegacyMongoShellV1Debug": "false",
"feature.enableLegacyMongoShellV2Debug": "false",
"feature.loadLegacyMongoShellFromBE": "false",
})
),
portalEnv: "prod",
});
queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`;
});
it("should return /mongoshell/indexv2.html by default ", () => {
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
});
it("should return /mongoshell/indexv2.html when portalEnv==localhost ", () => {
updateUserContext({
portalEnv: "localhost",
});
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
});
it("should return /mongoshell/index.html when enableLegacyMongoShellV1===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV1": "true",
})
),
});
expect(getMongoShellUrl()).toBe(`/mongoshell/index.html?${queryString}`);
});
it("should return /mongoshell/index.html when enableLegacyMongoShellV2===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV2": "true",
})
),
});
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
});
it("should return /mongoshell/index.html when enableLegacyMongoShellV1Debug===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV1Debug": "true",
})
),
});
expect(getMongoShellUrl()).toBe(`/mongoshell/debug/index.html?${queryString}`);
});
it("should return /mongoshell/index.html when enableLegacyMongoShellV2Debug===true", () => {
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.enableLegacyMongoShellV2Debug": "true",
})
),
});
expect(getMongoShellUrl()).toBe(`/mongoshell/debug/indexv2.html?${queryString}`);
});
describe("loadLegacyMongoShellFromBE===true", () => {
beforeEach(() => {
resetConfigContext();
updateConfigContext({
BACKEND_ENDPOINT: mongoBackendEndpoint,
platform: Platform.Hosted,
});
updateUserContext({
features: extractFeatures(
new URLSearchParams({
"feature.loadLegacyMongoShellFromBE": "true",
})
),
});
});
it("should return /mongoshell/index.html", () => {
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
});
it("configContext.platform !== Platform.Hosted, should return /mongoshell/indexv2.html", () => {
updateConfigContext({
platform: Platform.Portal,
});
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
});
it("configContext.BACKEND_ENDPOINT !== '' and configContext.platform !== Platform.Hosted, should return /mongoshell/indexv2.html", () => {
resetConfigContext();
updateConfigContext({
platform: Platform.Portal,
BACKEND_ENDPOINT: mongoBackendEndpoint,
});
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
});
it("configContext.BACKEND_ENDPOINT === '' and configContext.platform === Platform.Hosted, should return /mongoshell/indexv2.html ", () => {
resetConfigContext();
updateConfigContext({
platform: Platform.Hosted,
});
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
});
it("configContext.BACKEND_ENDPOINT === '' and configContext.platform !== Platform.Hosted, should return /mongoshell/indexv2.html", () => {
resetConfigContext();
updateConfigContext({
platform: Platform.Portal,
});
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
});
});
});
describe("getExtensionEndpoint", () => {
it("when platform === Platform.Hosted, backendEndpoint is undefined ", () => {
expect(getExtensionEndpoint(Platform.Hosted, undefined)).toBe("");
});
it("when platform === Platform.Hosted, backendEndpoint === ''", () => {
expect(getExtensionEndpoint(Platform.Hosted, "")).toBe("");
});
it("when platform === Platform.Hosted, backendEndpoint === null", () => {
expect(getExtensionEndpoint(Platform.Hosted, null)).toBe("");
});
it("when platform === Platform.Hosted, backendEndpoint != '' ", () => {
expect(getExtensionEndpoint(Platform.Hosted, "foo")).toBe("foo");
});
it("when platform === Platform.Portal, backendEndpoint is udefined ", () => {
expect(getExtensionEndpoint(Platform.Portal, undefined)).toBe("");
});
it("when platform === Platform.Portal, backendEndpoint === '' ", () => {
expect(getExtensionEndpoint(Platform.Portal, "")).toBe("");
});
it("when platform === Platform.Portal, backendEndpoint === null", () => {
expect(getExtensionEndpoint(Platform.Portal, null)).toBe("");
});
it("when platform !== Platform.Portal, backendEndpoint != '' ", () => {
expect(getExtensionEndpoint(Platform.Portal, "foo")).toBe("foo");
});
});

View File

@@ -1,45 +0,0 @@
import { configContext, Platform } from "../../../ConfigContext";
import { userContext } from "../../../UserContext";
export function getMongoShellUrl(): string {
const { databaseAccount: account } = userContext;
const resourceId = account?.id;
const accountName = account?.name;
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
if (userContext.features.enableLegacyMongoShellV1 === true) {
return `/mongoshell/index.html?${queryString}`;
}
if (userContext.features.enableLegacyMongoShellV1Debug === true) {
return `/mongoshell/debug/index.html?${queryString}`;
}
if (userContext.features.enableLegacyMongoShellV2 === true) {
return `/mongoshell/indexv2.html?${queryString}`;
}
if (userContext.features.enableLegacyMongoShellV2Debug === true) {
return `/mongoshell/debug/indexv2.html?${queryString}`;
}
if (userContext.portalEnv === "localhost") {
return `/mongoshell/indexv2.html?${queryString}`;
}
if (userContext.features.loadLegacyMongoShellFromBE === true) {
const extensionEndpoint: string = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
return `${extensionEndpoint}/content/mongoshell/debug/index.html?${queryString}`;
}
return `/mongoshell/indexv2.html?${queryString}`;
}
export function getExtensionEndpoint(platform: string, backendEndpoint: string): string {
const runtimeEndpoint = platform === Platform.Hosted ? backendEndpoint : "";
const extensionEndpoint: string = backendEndpoint || runtimeEndpoint || "";
return extensionEndpoint;
}

View File

@@ -70,19 +70,24 @@
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody> <tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
</table> </table>
</div> </div>
<button <div
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }" class="addClause"
style="border: none; background: none" role="button"
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }, attr: { title: addNewClauseLine }"
tabindex="0"
> >
<div class="addClause" data-bind=" "> <div class="addClause-heading">
<div class="addClause-heading"> <span class="clause-table addClause-title">
<span class="clause-table addClause-title"> <img
<img class="addclauseProperty-Img" style="margin-bottom: 5px" src="/Add-property.svg" /> class="addclauseProperty-Img"
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span> style="margin-bottom: 5px"
</span> src="/Add-property.svg"
</div> alt="Add new clause"
/>
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
</span>
</div> </div>
</button> </div>
</div> </div>
</div> </div>
<!-- Tables Query Tab Query Helper - End--> <!-- Tables Query Tab Query Helper - End-->
@@ -134,7 +139,6 @@
data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }" data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }"
tabindex="0" tabindex="0"
role="button" role="button"
href=""
> >
<span>Choose Columns... </span> <span>Choose Columns... </span>
</a> </a>
@@ -164,20 +168,22 @@
<script type="text/html" id="queryClause-template"> <script type="text/html" id="queryClause-template">
<tr class="clause-table-row"> <tr class="clause-table-row">
<td class="clause-table-cell action-column"> <td class="clause-table-cell action-column">
<button <span
class="entity-Add-Cancel"
role="button"
tabindex="0"
data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}" data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}"
> >
<span class="entity-Add-Cancel" role="button"> <img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" /> </span>
</span> <span
</button> class="entity-Add-Cancel"
<button role="button"
tabindex="0"
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}" data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}"
> >
<span class="entity-Add-Cancel" role="button"> <img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" /> </span>
</span>
</button>
</td> </td>
<td class="clause-table-cell group-control-column"> <td class="clause-table-cell group-control-column">
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" /> <input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" />

View File

@@ -7,10 +7,10 @@ import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab"; import { ConnectTab } from "Explorer/Tabs/ConnectTab";
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
import { userContext } from "UserContext";
import { useTeachingBubble } from "hooks/useTeachingBubble"; import { useTeachingBubble } from "hooks/useTeachingBubble";
import ko from "knockout"; import ko from "knockout";
import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { userContext } from "UserContext";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg"; import errorIcon from "../../../images/close-black.svg";
import { useObservable } from "../../hooks/useObservable"; import { useObservable } from "../../hooks/useObservable";
@@ -24,21 +24,22 @@ interface TabsProps {
} }
export const Tabs = ({ explorer }: TabsProps): JSX.Element => { export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
const { openedTabs, openedReactTabs, activeTab, activeReactTab, networkSettingsWarning } = useTabs(); const { openedTabs, openedReactTabs, activeTab, activeReactTab, showNetworkSettingsWarning } = useTabs();
return ( return (
<div className="tabsManagerContainer"> <div className="tabsManagerContainer">
{networkSettingsWarning && ( {showNetworkSettingsWarning && (
<MessageBar <MessageBar
messageBarType={MessageBarType.warning} messageBarType={MessageBarType.warning}
actions={ actions={
<MessageBarButton onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}> <MessageBarButton onClick={() => sendMessage({ type: MessageTypes.OpenCosmosDBNetworkingBlade })}>
Change network settings Change network settings
</MessageBarButton> </MessageBarButton>
} }
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }} messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
> >
{networkSettingsWarning} The Network settings for this account are preventing access from Data Explorer. Please allow access from Azure
Portal to proceed.
</MessageBar> </MessageBar>
)} )}
<div id="content" className="flexContainer hideOverflows"> <div id="content" className="flexContainer hideOverflows">
@@ -92,7 +93,6 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
} }
}} }}
className={active ? "active tabList" : "tabList"} className={active ? "active tabList" : "tabList"}
style={active ? { fontWeight: "bolder" } : {}}
title={useObservable(tab?.tabPath || ko.observable(""))} title={useObservable(tab?.tabPath || ko.observable(""))}
aria-selected={active} aria-selected={active}
aria-expanded={active} aria-expanded={active}
@@ -102,20 +102,22 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
ref={focusTab} ref={focusTab}
> >
<span className="tabNavContentContainer"> <span className="tabNavContentContainer">
<div className="tab_Content"> <a data-toggle="tab" href={"#" + tabId} tabIndex={-1}>
<span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}> <div className="tab_Content">
{useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />} <span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}>
{useObservable(tab?.isExecuting || ko.observable(false)) && ( {useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />}
<img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" /> {useObservable(tab?.isExecuting || ko.observable(false)) && (
)} <img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" />
</span> )}
<span className="tabNavText">{useObservable(tab?.tabTitle || ko.observable(ReactTabKind[tabKind]))}</span>
{tabKind !== ReactTabKind.Home && (
<span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
</span> </span>
)} <span className="tabNavText">{useObservable(tab?.tabTitle || ko.observable(ReactTabKind[tabKind]))}</span>
</div> {tabKind !== ReactTabKind.Home && (
<span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
</span>
)}
</div>
</a>
</span> </span>
</li> </li>
); );

View File

@@ -3,7 +3,7 @@ import React from "react";
import * as _ from "underscore"; import * as _ from "underscore";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { readCollections, readCollectionsWithPagination } from "../../Common/dataAccess/readCollections"; import { readCollections } from "../../Common/dataAccess/readCollections";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer"; import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
@@ -13,7 +13,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { useSidePanel } from "../../hooks/useSidePanel"; import { useSidePanel } from "../../hooks/useSidePanel";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient"; import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
import * as StorageUtility from "../../Shared/StorageUtility";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
@@ -39,7 +38,6 @@ export default class Database implements ViewModels.Database {
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>; public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public junoClient: JunoClient; public junoClient: JunoClient;
public isSampleDB: boolean; public isSampleDB: boolean;
public collectionsContinuationToken?: string;
private isOfferRead: boolean; private isOfferRead: boolean;
constructor(container: Explorer, data: DataModels.Database) { constructor(container: Explorer, data: DataModels.Database) {
@@ -142,11 +140,7 @@ export default class Database implements ViewModels.Database {
} }
await this.loadOffer(); await this.loadOffer();
await this.loadCollections();
if (this.collections()?.length === 0) {
await this.loadCollections(true);
}
this.isDatabaseExpanded(true); this.isDatabaseExpanded(true);
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
description: "Database node", description: "Database node",
@@ -168,31 +162,9 @@ export default class Database implements ViewModels.Database {
}); });
} }
public async loadCollections(restart = false) { public async loadCollections(): Promise<void> {
const collectionVMs: Collection[] = []; const collectionVMs: Collection[] = [];
let collections: DataModels.Collection[] = []; const collections: DataModels.Collection[] = await readCollections(this.id());
if (restart) {
this.collectionsContinuationToken = undefined;
}
const containerPaginationEnabled =
StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.ContainerPaginationEnabled) ===
"true";
if (containerPaginationEnabled) {
const collectionsWithPagination: DataModels.CollectionsWithPagination = await readCollectionsWithPagination(
this.id(),
this.collectionsContinuationToken
);
if (collectionsWithPagination.collections?.length === Constants.Queries.containersPerPage) {
this.collectionsContinuationToken = collectionsWithPagination.continuationToken;
} else {
this.collectionsContinuationToken = undefined;
}
collections = collectionsWithPagination.collections;
} else {
collections = await readCollections(this.id());
}
// TODO Remove // TODO Remove
// This is a hack to make Mongo collections read via ARM have a SQL-ish partitionKey property // This is a hack to make Mongo collections read via ARM have a SQL-ish partitionKey property
if (userContext.apiType === "Mongo" && userContext.authType === AuthType.AAD) { if (userContext.apiType === "Mongo" && userContext.authType === AuthType.AAD) {
@@ -227,9 +199,7 @@ export default class Database implements ViewModels.Database {
//merge collections //merge collections
this.addCollectionsToList(collectionVMs); this.addCollectionsToList(collectionVMs);
if (!containerPaginationEnabled || restart) { this.deleteCollectionsFromList(deltaCollections.toDelete);
this.deleteCollectionsFromList(deltaCollections.toDelete);
}
useDatabases.getState().updateDatabase(this); useDatabases.getState().updateDatabase(this);
} }

View File

@@ -479,18 +479,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
databaseNode.children.push(buildCollectionNode(database, collection)) databaseNode.children.push(buildCollectionNode(database, collection))
); );
if (database.collectionsContinuationToken) {
const loadMoreNode: TreeNode = {
label: "load more",
className: "loadMoreHeader",
onClick: async () => {
await database.loadCollections();
useDatabases.getState().updateDatabase(database);
},
};
databaseNode.children.push(loadMoreNode);
}
database.collections.subscribe((collections: ViewModels.Collection[]) => { database.collections.subscribe((collections: ViewModels.Collection[]) => {
collections.forEach((collection: ViewModels.Collection) => collections.forEach((collection: ViewModels.Collection) =>
databaseNode.children.push(buildCollectionNode(database, collection)) databaseNode.children.push(buildCollectionNode(database, collection))

View File

@@ -80,7 +80,6 @@ const App: React.FunctionComponent = () => {
return ( return (
<div className="flexContainer"> <div className="flexContainer">
<div id="divExplorer" className="flexContainer hideOverflows"> <div id="divExplorer" className="flexContainer hideOverflows">
<div id="freeTierTeachingBubble"> </div>
{/* Main Command Bar - Start */} {/* Main Command Bar - Start */}
<CommandBar container={explorer} /> <CommandBar container={explorer} />
{/* Collections Tree and Tabs - Begin */} {/* Collections Tree and Tabs - Begin */}

View File

@@ -29,12 +29,6 @@ export type Features = {
readonly mongoProxyEndpoint?: string; readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string; readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean; readonly enableThroughputCap: boolean;
readonly enableHierarchicalKeys: boolean;
readonly enableLegacyMongoShellV1: boolean;
readonly enableLegacyMongoShellV1Debug: boolean;
readonly enableLegacyMongoShellV2: boolean;
readonly enableLegacyMongoShellV2Debug: boolean;
readonly loadLegacyMongoShellFromBE: boolean;
// can be set via both flight and feature flag // can be set via both flight and feature flag
autoscaleDefault: boolean; autoscaleDefault: boolean;
@@ -96,12 +90,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
partitionKeyDefault2: "true" === get("pkpartitionkeytest"), partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
notebooksDownBanner: "true" === get("notebooksDownBanner"), notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"), enableThroughputCap: "true" === get("enablethroughputcap"),
enableHierarchicalKeys: "true" === get("enablehierarchicalkeys"),
enableLegacyMongoShellV1: "true" === get("enablelegacymongoshellv1"),
enableLegacyMongoShellV1Debug: "true" === get("enablelegacymongoshellv1debug"),
enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
}; };
} }

View File

@@ -4,7 +4,6 @@ import * as SessionStorageUtility from "./SessionStorageUtility";
export { LocalStorageUtility, SessionStorageUtility }; export { LocalStorageUtility, SessionStorageUtility };
export enum StorageKey { export enum StorageKey {
ActualItemPerPage, ActualItemPerPage,
ContainerPaginationEnabled,
CustomItemPerPage, CustomItemPerPage,
DatabaseAccountId, DatabaseAccountId,
EncryptedKeyToken, EncryptedKeyToken,

View File

@@ -49,7 +49,7 @@ export function getMsalInstance() {
cacheLocation: "localStorage", cacheLocation: "localStorage",
}, },
auth: { auth: {
authority: `${configContext.AAD_ENDPOINT}organizations`, authority: `${configContext.AAD_ENDPOINT}common`,
clientId: "203f1145-856a-4232-83d4-a43568fba23d", clientId: "203f1145-856a-4232-83d4-a43568fba23d",
}, },
}; };

View File

@@ -21,23 +21,6 @@ function isValidOrigin(allowedOrigins: ReadonlyArray<string>, event: MessageEven
return false; return false;
} }
export function shouldProcessMessage(event: MessageEvent): boolean {
if (typeof event.data !== "object") {
return false;
}
if (event.data["signature"] !== "pcIframe") {
return false;
}
if (!("data" in event.data)) {
return false;
}
if (typeof event.data["data"] !== "object") {
return false;
}
return true;
}
export function isReadyMessage(event: MessageEvent): boolean { export function isReadyMessage(event: MessageEvent): boolean {
if (!event?.data?.kind && !event?.data?.data) { if (!event?.data?.kind && !event?.data?.data) {
return false; return false;

View File

@@ -10,37 +10,31 @@ const PortalIPs: { [key: string]: string[] } = {
usnat: ["7.28.202.68"], usnat: ["7.28.202.68"],
}; };
export const getNetworkSettingsWarningMessage = (): string => { export const doNetworkSettingsAllowDataExplorerAccess = (): boolean => {
const accountProperties = userContext.databaseAccount?.properties; const accountProperties = userContext.databaseAccount?.properties;
if (!accountProperties) { if (!accountProperties) {
return ""; return false;
} }
// public network access is disabled // public network access is disabled
if (accountProperties.publicNetworkAccess !== "Enabled") { if (accountProperties.publicNetworkAccess !== "Enabled") {
return "The Network settings for this account are preventing access from Data Explorer. Please enable public access to proceed."; return false;
} }
const ipRules = accountProperties.ipRules; const ipRules = accountProperties.ipRules;
// public network access is set to "All networks" // public network access is set to "All networks"
if (ipRules.length === 0) { if (ipRules.length === 0) {
return ""; return true;
} }
if (userContext.apiType === "Cassandra" || userContext.apiType === "Mongo") { const portalIPs = PortalIPs[userContext.portalEnv];
const portalIPs = PortalIPs[userContext.portalEnv]; let numberOfMatches = 0;
let numberOfMatches = 0; ipRules.forEach((ipRule) => {
ipRules.forEach((ipRule) => { if (portalIPs.indexOf(ipRule.ipAddressOrRange) !== -1) {
if (portalIPs.indexOf(ipRule.ipAddressOrRange) !== -1) { numberOfMatches++;
numberOfMatches++;
}
});
if (numberOfMatches !== portalIPs.length) {
return "The Network settings for this account are preventing access from Data Explorer. Please allow access from Azure Portal to proceed.";
} }
} });
return ""; return numberOfMatches === portalIPs.length;
}; };

View File

@@ -1,7 +1,6 @@
import Explorer from "Explorer/Explorer";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; import { doNetworkSettingsAllowDataExplorerAccess } from "Utils/NetworkUtility";
import { applyExplorerBindings } from "../applyExplorerBindings"; import { applyExplorerBindings } from "../applyExplorerBindings";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { AccountKind, Flights } from "../Common/Constants"; import { AccountKind, Flights } from "../Common/Constants";
@@ -11,6 +10,7 @@ import { configContext, Platform, updateConfigContext } from "../ConfigContext";
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts"; import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { DataExplorerInputsFrame } from "../Contracts/ViewModels"; import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer";
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions"; import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
import { useDatabases } from "../Explorer/useDatabases"; import { useDatabases } from "../Explorer/useDatabases";
import { import {
@@ -33,7 +33,7 @@ import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext"
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types"; import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
import { getMsalInstance } from "../Utils/AuthorizationUtils"; import { getMsalInstance } from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
// This hook will create a new instance of Explorer.ts and bind it to the DOM // This hook will create a new instance of Explorer.ts and bind it to the DOM
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React // This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
@@ -239,7 +239,6 @@ async function configurePortal(): Promise<Explorer> {
updateUserContext({ updateUserContext({
authType: AuthType.AAD, authType: AuthType.AAD,
}); });
let explorer: Explorer;
return new Promise((resolve) => { return new Promise((resolve) => {
// In development mode, try to load the iframe message from session storage. // In development mode, try to load the iframe message from session storage.
// This allows webpack hot reload to function properly in the portal // This allows webpack hot reload to function properly in the portal
@@ -252,7 +251,7 @@ async function configurePortal(): Promise<Explorer> {
); );
console.dir(message); console.dir(message);
updateContextsFromPortalMessage(message); updateContextsFromPortalMessage(message);
explorer = new Explorer(); const explorer = new Explorer();
// In development mode, save the iframe message from the portal in session storage. // In development mode, save the iframe message from the portal in session storage.
// This allows webpack hot reload to funciton properly // This allows webpack hot reload to funciton properly
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
@@ -288,7 +287,7 @@ async function configurePortal(): Promise<Explorer> {
} }
updateContextsFromPortalMessage(inputs); updateContextsFromPortalMessage(inputs);
explorer = new Explorer(); const explorer = new Explorer();
resolve(explorer); resolve(explorer);
if (openAction) { if (openAction) {
handleOpenAction(openAction, useDatabases.getState().databases, explorer); handleOpenAction(openAction, useDatabases.getState().databases, explorer);
@@ -301,8 +300,6 @@ async function configurePortal(): Promise<Explorer> {
} else { } else {
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId); useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
} }
} else if (message?.type === MessageTypes.RefreshResources) {
explorer.onRefreshResourcesClick();
} }
}, },
false false
@@ -317,6 +314,23 @@ function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
return messageOrigin === window.document.location.origin && message.type === MessageTypes.TelemetryInfo; return messageOrigin === window.document.location.origin && message.type === MessageTypes.TelemetryInfo;
} }
function shouldProcessMessage(event: MessageEvent): boolean {
if (typeof event.data !== "object") {
return false;
}
if (event.data["signature"] !== "pcIframe") {
return false;
}
if (!("data" in event.data)) {
return false;
}
if (typeof event.data["data"] !== "object") {
return false;
}
return true;
}
function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if ( if (
configContext.BACKEND_ENDPOINT && configContext.BACKEND_ENDPOINT &&
@@ -368,8 +382,8 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
} }
} }
const warningMessage = getNetworkSettingsWarningMessage(); const isDataExplorerAccessAllowed = doNetworkSettingsAllowDataExplorerAccess();
useTabs.getState().setNetworkSettingsWarning(warningMessage); useTabs.getState().setShowNetworkSettingsWarning(!isDataExplorerAccessAllowed);
if (inputs.features) { if (inputs.features) {
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features))); Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));

View File

@@ -9,7 +9,7 @@ interface TabsState {
openedReactTabs: ReactTabKind[]; openedReactTabs: ReactTabKind[];
activeTab: TabsBase | undefined; activeTab: TabsBase | undefined;
activeReactTab: ReactTabKind | undefined; activeReactTab: ReactTabKind | undefined;
networkSettingsWarning: string; showNetworkSettingsWarning: boolean;
activateTab: (tab: TabsBase) => void; activateTab: (tab: TabsBase) => void;
activateNewTab: (tab: TabsBase) => void; activateNewTab: (tab: TabsBase) => void;
activateReactTab: (tabkind: ReactTabKind) => void; activateReactTab: (tabkind: ReactTabKind) => void;
@@ -21,7 +21,7 @@ interface TabsState {
closeAllNotebookTabs: (hardClose: boolean) => void; closeAllNotebookTabs: (hardClose: boolean) => void;
openAndActivateReactTab: (tabKind: ReactTabKind) => void; openAndActivateReactTab: (tabKind: ReactTabKind) => void;
closeReactTab: (tabKind: ReactTabKind) => void; closeReactTab: (tabKind: ReactTabKind) => void;
setNetworkSettingsWarning: (warningMessage: string) => void; setShowNetworkSettingsWarning: (showWarning: boolean) => void;
} }
export enum ReactTabKind { export enum ReactTabKind {
@@ -35,7 +35,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
openedReactTabs: [ReactTabKind.Home], openedReactTabs: [ReactTabKind.Home],
activeTab: undefined, activeTab: undefined,
activeReactTab: ReactTabKind.Home, activeReactTab: ReactTabKind.Home,
networkSettingsWarning: "", showNetworkSettingsWarning: false,
activateTab: (tab: TabsBase): void => { activateTab: (tab: TabsBase): void => {
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) { if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
set({ activeTab: tab, activeReactTab: undefined }); set({ activeTab: tab, activeReactTab: undefined });
@@ -145,5 +145,5 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
set({ openedReactTabs: updatedOpenedReactTabs }); set({ openedReactTabs: updatedOpenedReactTabs });
}, },
setNetworkSettingsWarning: (warningMessage: string) => set({ networkSettingsWarning: warningMessage }), setShowNetworkSettingsWarning: (showWarning: boolean) => set({ showNetworkSettingsWarning: showWarning }),
})); }));