Compare commits

..

3 Commits

Author SHA1 Message Date
Victor Meng
af5fd699f9 Increase the video width 2022-09-16 18:25:02 -07:00
Victor Meng
d78610cd33 Remove feature flag 2022-09-14 18:27:09 -07:00
Victor Meng
bc99ca372b Hardcode API type to Postgres to test Postgres quick start 2022-09-14 17:21:26 -07:00
111 changed files with 1311 additions and 9265 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 "GitHub" -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 "GitHub" -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,6 +1,5 @@
<!doctype html> <!doctype html>
<html class="default no-js"> <html class="default no-js">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -10,7 +9,6 @@
<link rel="stylesheet" href="../assets/css/main.css"> <link rel="stylesheet" href="../assets/css/main.css">
<script async src="../assets/js/search.js" id="search-script"></script> <script async src="../assets/js/search.js" id="search-script"></script>
</head> </head>
<body> <body>
<header> <header>
<div class="tsd-page-toolbar"> <div class="tsd-page-toolbar">
@@ -84,24 +82,12 @@
<section class="tsd-index-section "> <section class="tsd-index-section ">
<h3>Enumeration members</h3> <h3>Enumeration members</h3>
<ul class="tsd-index-list"> <ul class="tsd-index-list">
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a <li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="selfserve_selfserveutils.bladetype.html#cassandrakeys" class="tsd-kind-icon">Cassandra<wbr>Keys</a></li>
href="selfserve_selfserveutils.bladetype.html#cassandrakeys" <li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="selfserve_selfserveutils.bladetype.html#gremlinkeys" class="tsd-kind-icon">Gremlin<wbr>Keys</a></li>
class="tsd-kind-icon">Cassandra<wbr>Keys</a></li> <li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="selfserve_selfserveutils.bladetype.html#metrics" class="tsd-kind-icon">Metrics</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a <li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="selfserve_selfserveutils.bladetype.html#mongokeys" class="tsd-kind-icon">Mongo<wbr>Keys</a></li>
href="selfserve_selfserveutils.bladetype.html#gremlinkeys" <li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="selfserve_selfserveutils.bladetype.html#sqlkeys" class="tsd-kind-icon">Sql<wbr>Keys</a></li>
class="tsd-kind-icon">Gremlin<wbr>Keys</a></li> <li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="selfserve_selfserveutils.bladetype.html#tablekeys" class="tsd-kind-icon">Table<wbr>Keys</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a
href="selfserve_selfserveutils.bladetype.html#metrics"
class="tsd-kind-icon">Metrics</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a
href="selfserve_selfserveutils.bladetype.html#mongokeys"
class="tsd-kind-icon">Mongo<wbr>Keys</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a
href="selfserve_selfserveutils.bladetype.html#sqlkeys"
class="tsd-kind-icon">Sql<wbr>Keys</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a
href="selfserve_selfserveutils.bladetype.html#tablekeys"
class="tsd-kind-icon">Table<wbr>Keys</a></li>
</ul> </ul>
</section> </section>
</div> </div>
@@ -112,36 +98,31 @@
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum"> <section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="cassandrakeys" class="tsd-anchor"></a> <a name="cassandrakeys" class="tsd-anchor"></a>
<h3>Cassandra<wbr>Keys</h3> <h3>Cassandra<wbr>Keys</h3>
<div class="tsd-signature tsd-kind-icon">Cassandra<wbr>Keys<span <div class="tsd-signature tsd-kind-icon">Cassandra<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;cassandraDbKeys&quot;</span></div>
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
&quot;cassandraDbKeys&quot;</span></div>
<aside class="tsd-sources"> <aside class="tsd-sources">
</aside> </aside>
<div class="tsd-comment tsd-typography"> <div class="tsd-comment tsd-typography">
<div class="lead"> <div class="lead">
<p>Keys blade of a Azure Cosmos DB for Apache Cassandra account.</p> <p>Keys blade of a Cassandra API account.</p>
</div> </div>
</div> </div>
</section> </section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum"> <section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="gremlinkeys" class="tsd-anchor"></a> <a name="gremlinkeys" class="tsd-anchor"></a>
<h3>Gremlin<wbr>Keys</h3> <h3>Gremlin<wbr>Keys</h3>
<div class="tsd-signature tsd-kind-icon">Gremlin<wbr>Keys<span <div class="tsd-signature tsd-kind-icon">Gremlin<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;keys&quot;</span></div>
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
&quot;keys&quot;</span></div>
<aside class="tsd-sources"> <aside class="tsd-sources">
</aside> </aside>
<div class="tsd-comment tsd-typography"> <div class="tsd-comment tsd-typography">
<div class="lead"> <div class="lead">
<p>Keys blade of a Azure Cosmos DB for Apache Gremlin account.</p> <p>Keys blade of a Gremlin API account.</p>
</div> </div>
</div> </div>
</section> </section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum"> <section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="metrics" class="tsd-anchor"></a> <a name="metrics" class="tsd-anchor"></a>
<h3>Metrics</h3> <h3>Metrics</h3>
<div class="tsd-signature tsd-kind-icon">Metrics<span class="tsd-signature-symbol">:</span> <div class="tsd-signature tsd-kind-icon">Metrics<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;metrics&quot;</span></div>
<span class="tsd-signature-symbol"> = &quot;metrics&quot;</span></div>
<aside class="tsd-sources"> <aside class="tsd-sources">
</aside> </aside>
<div class="tsd-comment tsd-typography"> <div class="tsd-comment tsd-typography">
@@ -153,41 +134,36 @@
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum"> <section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="mongokeys" class="tsd-anchor"></a> <a name="mongokeys" class="tsd-anchor"></a>
<h3>Mongo<wbr>Keys</h3> <h3>Mongo<wbr>Keys</h3>
<div class="tsd-signature tsd-kind-icon">Mongo<wbr>Keys<span <div class="tsd-signature tsd-kind-icon">Mongo<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;mongoDbKeys&quot;</span></div>
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
&quot;mongoDbKeys&quot;</span></div>
<aside class="tsd-sources"> <aside class="tsd-sources">
</aside> </aside>
<div class="tsd-comment tsd-typography"> <div class="tsd-comment tsd-typography">
<div class="lead"> <div class="lead">
<p>Keys blade of a Azure Cosmos DB for MongoDB account.</p> <p>Keys blade of a Mongo API account.</p>
</div> </div>
</div> </div>
</section> </section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum"> <section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="sqlkeys" class="tsd-anchor"></a> <a name="sqlkeys" class="tsd-anchor"></a>
<h3>Sql<wbr>Keys</h3> <h3>Sql<wbr>Keys</h3>
<div class="tsd-signature tsd-kind-icon">Sql<wbr>Keys<span class="tsd-signature-symbol">:</span> <div class="tsd-signature tsd-kind-icon">Sql<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;keys&quot;</span></div>
<span class="tsd-signature-symbol"> = &quot;keys&quot;</span></div>
<aside class="tsd-sources"> <aside class="tsd-sources">
</aside> </aside>
<div class="tsd-comment tsd-typography"> <div class="tsd-comment tsd-typography">
<div class="lead"> <div class="lead">
<p>Keys blade of a Azure Cosmos DB for NoSQL account.</p> <p>Keys blade of a SQL API account.</p>
</div> </div>
</div> </div>
</section> </section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum"> <section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="tablekeys" class="tsd-anchor"></a> <a name="tablekeys" class="tsd-anchor"></a>
<h3>Table<wbr>Keys</h3> <h3>Table<wbr>Keys</h3>
<div class="tsd-signature tsd-kind-icon">Table<wbr>Keys<span <div class="tsd-signature tsd-kind-icon">Table<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;tableKeys&quot;</span></div>
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
&quot;tableKeys&quot;</span></div>
<aside class="tsd-sources"> <aside class="tsd-sources">
</aside> </aside>
<div class="tsd-comment tsd-typography"> <div class="tsd-comment tsd-typography">
<div class="lead"> <div class="lead">
<p>Keys blade of a Azure Cosmos DB for Table account.</p> <p>Keys blade of a Table API account.</p>
</div> </div>
</div> </div>
</section> </section>
@@ -203,23 +179,19 @@
<a href="../modules/selfserve.html">Self<wbr>Serve</a> <a href="../modules/selfserve.html">Self<wbr>Serve</a>
</li> </li>
<li class=" tsd-kind-module"> <li class=" tsd-kind-module">
<a href="../modules/selfserve___what_is_currently_supported_.html">Self<wbr>Serve -<wbr> <a href="../modules/selfserve___what_is_currently_supported_.html">Self<wbr>Serve -<wbr> <wbr>What is currently supported?</a>
<wbr>What is currently supported?</a>
</li> </li>
<li class=" tsd-kind-module"> <li class=" tsd-kind-module">
<a href="../modules/selfserve_decorators.html">Self<wbr>Serve/<wbr>Decorators</a> <a href="../modules/selfserve_decorators.html">Self<wbr>Serve/<wbr>Decorators</a>
</li> </li>
<li class=" tsd-kind-module"> <li class=" tsd-kind-module">
<a <a href="../modules/selfserve_selfservetelemetryprocessor.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Telemetry<wbr>Processor</a>
href="../modules/selfserve_selfservetelemetryprocessor.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Telemetry<wbr>Processor</a>
</li> </li>
<li class=" tsd-kind-module"> <li class=" tsd-kind-module">
<a <a href="../modules/selfserve_selfservetypes.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Types</a>
href="../modules/selfserve_selfservetypes.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Types</a>
</li> </li>
<li class="current tsd-kind-module"> <li class="current tsd-kind-module">
<a <a href="../modules/selfserve_selfserveutils.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Utils</a>
href="../modules/selfserve_selfserveutils.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Utils</a>
</li> </li>
</ul> </ul>
</nav> </nav>
@@ -231,40 +203,32 @@
<a href="selfserve_selfserveutils.bladetype.html" class="tsd-kind-icon">Blade<wbr>Type</a> <a href="selfserve_selfserveutils.bladetype.html" class="tsd-kind-icon">Blade<wbr>Type</a>
<ul> <ul>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum"> <li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="selfserve_selfserveutils.bladetype.html#cassandrakeys" <a href="selfserve_selfserveutils.bladetype.html#cassandrakeys" class="tsd-kind-icon">Cassandra<wbr>Keys</a>
class="tsd-kind-icon">Cassandra<wbr>Keys</a>
</li> </li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum"> <li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="selfserve_selfserveutils.bladetype.html#gremlinkeys" <a href="selfserve_selfserveutils.bladetype.html#gremlinkeys" class="tsd-kind-icon">Gremlin<wbr>Keys</a>
class="tsd-kind-icon">Gremlin<wbr>Keys</a>
</li> </li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum"> <li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="selfserve_selfserveutils.bladetype.html#metrics" <a href="selfserve_selfserveutils.bladetype.html#metrics" class="tsd-kind-icon">Metrics</a>
class="tsd-kind-icon">Metrics</a>
</li> </li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum"> <li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="selfserve_selfserveutils.bladetype.html#mongokeys" <a href="selfserve_selfserveutils.bladetype.html#mongokeys" class="tsd-kind-icon">Mongo<wbr>Keys</a>
class="tsd-kind-icon">Mongo<wbr>Keys</a>
</li> </li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum"> <li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="selfserve_selfserveutils.bladetype.html#sqlkeys" <a href="selfserve_selfserveutils.bladetype.html#sqlkeys" class="tsd-kind-icon">Sql<wbr>Keys</a>
class="tsd-kind-icon">Sql<wbr>Keys</a>
</li> </li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum"> <li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="selfserve_selfserveutils.bladetype.html#tablekeys" <a href="selfserve_selfserveutils.bladetype.html#tablekeys" class="tsd-kind-icon">Table<wbr>Keys</a>
class="tsd-kind-icon">Table<wbr>Keys</a>
</li> </li>
</ul> </ul>
</li> </li>
</ul> </ul>
<ul class="after-current"> <ul class="after-current">
<li class=" tsd-kind-enum tsd-parent-kind-module"> <li class=" tsd-kind-enum tsd-parent-kind-module">
<a href="selfserve_selfserveutils.selfservetype.html" <a href="selfserve_selfserveutils.selfservetype.html" class="tsd-kind-icon">Self<wbr>Serve<wbr>Type</a>
class="tsd-kind-icon">Self<wbr>Serve<wbr>Type</a>
</li> </li>
<li class=" tsd-kind-function tsd-parent-kind-module"> <li class=" tsd-kind-function tsd-parent-kind-module">
<a href="../modules/selfserve_selfserveutils.html#generatebladelink" <a href="../modules/selfserve_selfserveutils.html#generatebladelink" class="tsd-kind-icon">generate<wbr>Blade<wbr>Link</a>
class="tsd-kind-icon">generate<wbr>Blade<wbr>Link</a>
</li> </li>
</ul> </ul>
</nav> </nav>
@@ -297,5 +261,4 @@
<div class="overlay"></div> <div class="overlay"></div>
<script src="../assets/js/main.js"></script> <script src="../assets/js/main.js"></script>
</body> </body>
</html> </html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

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

@@ -6,7 +6,7 @@
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}]*/
@@ -45,7 +45,7 @@
/*[{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}]*/
} }
@@ -70,14 +70,14 @@
} }
.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,
@@ -120,7 +120,7 @@
.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;
@@ -201,9 +201,9 @@ input::-webkit-inner-spin-button {
.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;
@@ -299,9 +299,9 @@ input::-webkit-inner-spin-button {
} }
.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%;
@@ -366,7 +366,7 @@ input::-webkit-inner-spin-button {
.group-indicator-table { .group-indicator-table {
border-spacing: 0px; border-spacing: 0px;
min-height: 24px; min-height: 24px
} }
.group-indicator-column { .group-indicator-column {
@@ -396,6 +396,7 @@ input::-webkit-inner-spin-button {
background-color: #ffffff; background-color: #ffffff;
} }
/*.type-header { /*.type-header {
padding-right: 4px; padding-right: 4px;
} }
@@ -409,9 +410,9 @@ 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}]*/
} }
@@ -461,11 +462,11 @@ input::-webkit-inner-spin-button {
.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,
@@ -484,7 +485,7 @@ input::-webkit-inner-spin-button {
} }
.entity-Add-Cancel { .entity-Add-Cancel {
// padding: @DefaultSpace @SmallSpace @SmallSpace; padding: @DefaultSpace @SmallSpace @SmallSpace;
cursor: pointer; cursor: pointer;
} }
@@ -497,7 +498,7 @@ input::-webkit-inner-spin-button {
} }
.query-builder-isDisabled { .query-builder-isDisabled {
border: 1px solid #cccedb; border: 1px solid #CCCEDB;
color: #ccc; color: #ccc;
} }
@@ -514,6 +515,7 @@ input::-webkit-inner-spin-button {
margin-bottom: 5px; margin-bottom: 5px;
} }
/* /*
@media only screen and (max-width: 1200px) { @media only screen and (max-width: 1200px) {
.clause-table { .clause-table {

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 {
@@ -29,3 +24,5 @@
pointer-events: none; pointer-events: none;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react"; import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import { getApiShortDisplayName } from "../Utils/APITypeUtils"; import { userContext } from "../UserContext";
import { NormalizedEventKey } from "./Constants"; import { NormalizedEventKey } from "./Constants";
export interface CollapsedResourceTreeProps { export interface CollapsedResourceTreeProps {
@@ -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}
@@ -45,7 +45,7 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" /> <img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
</span> </span>
<span className="collectionCollapsed"> <span className="collectionCollapsed">
<span>{getApiShortDisplayName()}</span> <span>{userContext.apiType} API</span>
</span> </span>
</li> </li>
</ul> </ul>

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

@@ -6,7 +6,6 @@ import Explorer from "../Explorer/Explorer";
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree"; import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
import { ResourceTree } from "../Explorer/Tree/ResourceTree"; import { ResourceTree } from "../Explorer/Tree/ResourceTree";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
import { NormalizedEventKey } from "./Constants"; import { NormalizedEventKey } from "./Constants";
export interface ResourceTreeContainerProps { export interface ResourceTreeContainerProps {
@@ -43,7 +42,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
{/* Collections Window Title/Command Bar - Start */} {/* Collections Window Title/Command Bar - Start */}
<div className="collectiontitle"> <div className="collectiontitle">
<div className="coltitle"> <div className="coltitle">
<span className="titlepadcol">{getApiShortDisplayName()}</span> <span className="titlepadcol">{userContext.apiType} API</span>
<div className="float-right"> <div className="float-right">
<span <span
className="padimgcolrefresh" className="padimgcolrefresh"
@@ -51,7 +50,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 +62,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

@@ -33,8 +33,6 @@ export interface DatabaseAccountExtendedProperties {
privateEndpointConnections?: unknown[]; privateEndpointConnections?: unknown[];
capacity?: { totalThroughputLimit: number }; capacity?: { totalThroughputLimit: number };
locations?: DatabaseAccountResponseLocation[]; locations?: DatabaseAccountResponseLocation[];
postgresqlEndpoint?: string;
publicNetworkAccess?: string;
} }
export interface DatabaseAccountResponseLocation { export interface DatabaseAccountResponseLocation {
@@ -156,11 +154,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[];
} }
@@ -573,16 +566,6 @@ export interface ContainerConnectionInfo {
//need to add ram and rom info //need to add ram and rom info
} }
export interface PostgresFirewallRule {
id: string;
name: string;
type: string;
properties: {
startIpAddress: string;
endIpAddress: string;
};
}
export enum PhoenixErrorType { export enum PhoenixErrorType {
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded", MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded", MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",

View File

@@ -35,9 +35,6 @@ export enum MessageTypes {
RefreshDatabaseAccount, RefreshDatabaseAccount,
CloseTab, CloseTab,
OpenQuickstartBlade, OpenQuickstartBlade,
OpenPostgreSQLPasswordReset,
OpenPostgresNetworkingBlade,
OpenCosmosDBNetworkingBlade,
} }
export { Versions, ActionContracts, Diagnostics }; export { Versions, ActionContracts, Diagnostics };

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;
@@ -186,6 +186,7 @@ export interface Collection extends CollectionBase {
onDrop(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>; uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
getLabel(): string;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>; getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
} }
@@ -371,7 +372,6 @@ export enum TerminalKind {
Default = 0, Default = 0,
Mongo = 1, Mongo = 1,
Cassandra = 2, Cassandra = 2,
Postgres = 3,
} }
export interface DataExplorerInputsFrame { export interface DataExplorerInputsFrame {
@@ -395,11 +395,6 @@ export interface DataExplorerInputsFrame {
sharedThroughputDefault?: number; sharedThroughputDefault?: number;
dataExplorerVersion?: string; dataExplorerVersion?: string;
defaultCollectionThroughput?: CollectionCreationDefaults; defaultCollectionThroughput?: CollectionCreationDefaults;
isPostgresAccount?: 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
connectionStringParams?: any;
flights?: readonly string[]; flights?: readonly string[];
features?: { features?: {
[key: string]: string; [key: string]: string;

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

@@ -2,7 +2,6 @@
* Wrapper around Notebook server terminal * Wrapper around Notebook server terminal
*/ */
import { useTerminal } from "hooks/useTerminal";
import postRobot from "post-robot"; import postRobot from "post-robot";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
@@ -41,7 +40,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void { handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow; this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
useTerminal.getState().setTerminal(this.terminalWindow);
this.sendPropsToTerminalFrame(); this.sendPropsToTerminalFrame();
} }
@@ -76,8 +74,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint; this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) { } else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint; terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
return this.props.databaseAccount?.properties.postgresqlEndpoint;
} }
if (terminalEndpoint) { if (terminalEndpoint) {

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

@@ -35,7 +35,6 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,
@@ -112,7 +111,6 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,

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,11 +185,9 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack> </Stack>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<div role="radiogroup">
<input <input
id="Autoscale-input"
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
aria-label="Autoscale database throughput" aria-label="Autoscale mode"
aria-required={true} aria-required={true}
checked={isAutoscaleSelected} checked={isAutoscaleSelected}
type="radio" type="radio"
@@ -197,14 +195,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")} onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/> />
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel"> <span className="throughputInputRadioBtnLabel">Autoscale</span>
Autoscale
</label>
<input <input
id="Manual-input"
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
aria-label="Manual database throughput" aria-label="Manual mode"
checked={!isAutoscaleSelected} checked={!isAutoscaleSelected}
type="radio" type="radio"
aria-required={true} aria-required={true}
@@ -212,22 +207,14 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Manual")} onChange={(e) => handleOnChangeMode(e, "Manual")}
/> />
<label className="throughputInputRadioBtnLabel" htmlFor="Manual-input"> <span className="throughputInputRadioBtnLabel">Manual</span>
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"
@@ -653,46 +653,41 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
> >
<div <div
className="ms-Stack css-58" className="ms-Stack css-58"
>
<div
key=".0:$.0"
role="radiogroup"
> >
<input <input
aria-label="Autoscale database throughput" aria-label="Autoscale mode"
aria-required={true} aria-required={true}
checked={true} checked={true}
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
id="Autoscale-input" key=".0:$.0"
onChange={[Function]} onChange={[Function]}
role="radio" role="radio"
tabIndex={0} tabIndex={0}
type="radio" type="radio"
/> />
<label <span
className="throughputInputRadioBtnLabel" className="throughputInputRadioBtnLabel"
htmlFor="Autoscale-input" key=".0:$.1"
> >
Autoscale Autoscale
</label> </span>
<input <input
aria-label="Manual database throughput" aria-label="Manual mode"
aria-required={true} aria-required={true}
checked={false} checked={false}
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
id="Manual-input" key=".0:$.2"
onChange={[Function]} onChange={[Function]}
role="radio" role="radio"
tabIndex={0} tabIndex={0}
type="radio" type="radio"
/> />
<label <span
className="throughputInputRadioBtnLabel" className="throughputInputRadioBtnLabel"
htmlFor="Manual-input" key=".0:$.3"
> >
Manual Manual
</label> </span>
</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

@@ -93,8 +93,7 @@ export default class Explorer {
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}); });
this._isInitializingNotebooks = false; this._isInitializingNotebooks = false;
this.phoenixClient = new PhoenixClient();
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
useNotebook.subscribe( useNotebook.subscribe(
() => this.refreshCommandBarButtons(), () => this.refreshCommandBarButtons(),
(state) => state.isNotebooksEnabledForAccount (state) => state.isNotebooksEnabledForAccount
@@ -186,8 +185,10 @@ export default class Explorer {
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath); useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
} }
if (userContext.apiType !== "Postgres") {
this.refreshExplorer(); this.refreshExplorer();
} }
}
public async initiateAndRefreshNotebookList(): Promise<void> { public async initiateAndRefreshNotebookList(): Promise<void> {
if (!this.notebookManager) { if (!this.notebookManager) {
@@ -352,7 +353,7 @@ export default class Explorer {
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined)) (notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
) { ) {
const provisionData: IProvisionData = { const provisionData: IProvisionData = {
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint, cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
poolId: PoolIdType.DefaultPoolId, poolId: PoolIdType.DefaultPoolId,
}; };
const connectionStatus: ContainerConnectionInfo = { const connectionStatus: ContainerConnectionInfo = {
@@ -577,7 +578,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();
@@ -1057,10 +1058,6 @@ export default class Explorer {
title = "Cassandra Shell"; title = "Cassandra Shell";
break; break;
case ViewModels.TerminalKind.Postgres:
title = "PSQL Shell";
break;
default: default:
throw new Error("Terminal kind: ${kind} not supported"); throw new Error("Terminal kind: ${kind} not supported");
} }
@@ -1247,11 +1244,9 @@ export default class Explorer {
} }
public async refreshExplorer(): Promise<void> { public async refreshExplorer(): Promise<void> {
if (userContext.apiType !== "Postgres") {
userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases(); : this.refreshAllDatabases();
}
await useNotebook.getState().refreshNotebooksEnabledStateForAccount(); await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount // TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount

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,14 +51,12 @@ 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);
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) { if (addSynapseLink) {
buttons.push(createDivider()); buttons.push(createDivider());
buttons.push(addSynapseLink); buttons.push(addSynapseLink);
} }
}
if (userContext.apiType !== "Tables") { if (userContext.apiType !== "Tables") {
newCollectionBtn.children = [createNewCollectionGroup(container)]; newCollectionBtn.children = [createNewCollectionGroup(container)];
@@ -204,7 +202,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: () => {
@@ -526,28 +523,6 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
}; };
} }
function createOpenPsqlTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open PSQL Shell";
const disableButton =
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Postgres);
}
},
commandButtonLabel: label,
hasPopup: false,
disabled: disableButton,
ariaLabel: label,
tooltipText: !disableButton
? ""
: "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.",
};
}
function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps {
const label = "Reset Workspace"; const label = "Reset Workspace";
return { return {
@@ -612,7 +587,16 @@ function createStaticCommandBarButtonsForResourceToken(
} }
export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] { export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] {
const openPostgreShellBtn = createOpenPsqlTerminalButton(container); const postgreShellLabel = "Open PostgreSQL Shell";
const openPostgreShellBtn = {
iconSrc: HostedTerminalIcon,
iconAlt: postgreShellLabel,
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Mongo),
commandButtonLabel: postgreShellLabel,
hasPopup: false,
disabled: false,
ariaLabel: postgreShellLabel,
};
return [openPostgreShellBtn]; return [openPostgreShellBtn];
} }

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

@@ -23,7 +23,7 @@ export class NotebookContainerClient {
private scheduleTimerId: NodeJS.Timeout; private scheduleTimerId: NodeJS.Timeout;
constructor(private onConnectionLost: () => void) { constructor(private onConnectionLost: () => void) {
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id); this.phoenixClient = new PhoenixClient();
this.retryOptions = { this.retryOptions = {
retries: Notebook.retryAttempts, retries: Notebook.retryAttempts,
maxTimeout: Notebook.retryAttemptDelayMs, maxTimeout: Notebook.retryAttemptDelayMs,

View File

@@ -124,9 +124,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
} }
const firstWriteLocation = const firstWriteLocation =
userContext.apiType === "Postgres" databaseAccount?.properties?.writeLocations &&
? databaseAccount?.location databaseAccount?.properties?.writeLocations[0]?.locationName.toLowerCase();
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`; const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
const authorizationHeader = getAuthorizationHeader(); const authorizationHeader = getAuthorizationHeader();
try { try {
@@ -308,16 +307,13 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
let isPhoenixFeatures = false; let isPhoenixFeatures = false;
const isPublicInternetAllowed = isPublicInternetAccessAllowed(); const isPublicInternetAllowed = isPublicInternetAccessAllowed();
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id); const phoenixClient = new PhoenixClient();
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus(); const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) { if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) { if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true; isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
isPhoenixFeatures = isPhoenixFeatures = isPublicInternetAllowed && userContext.features.phoenixFeatures === true;
isPublicInternetAllowed &&
// phoenix needs to be enabled for Postgres accounts since the PSQL shell requires phoenix containers
(userContext.features.phoenixFeatures === true || userContext.apiType === "Postgres");
} else { } else {
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed; isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
} }

View File

@@ -1,9 +1,17 @@
jest.mock("../hooks/useFullScreenURLs");
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react"; import { render, screen } from "@testing-library/react";
import React from "react"; import React from "react";
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
import { OpenFullScreen } from "./OpenFullScreen"; import { OpenFullScreen } from "./OpenFullScreen";
it("renders the correct URLs", () => { it("renders the correct URLs", () => {
render(<OpenFullScreen />); (useFullScreenURLs as jest.Mock).mockReturnValue({
expect(screen.getByText("Open")).toBeDefined(); readWrite: "read and write url",
read: "read only url",
});
render(<OpenFullScreen />);
expect(screen.getByLabelText("Read and Write")).toHaveValue("https://cosmos.azure.com/?key=read and write url");
expect(screen.getByLabelText("Read Only")).toHaveValue("https://cosmos.azure.com/?key=read only url");
}); });

View File

@@ -1,26 +1,66 @@
import { PrimaryButton, Stack, Text } from "@fluentui/react"; import { DefaultButton, PrimaryButton, Spinner, Stack, Text, TextField } from "@fluentui/react";
import copyToClipboard from "clipboard-copy";
import * as React from "react"; import * as React from "react";
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
export const OpenFullScreen: React.FunctionComponent = () => { export const OpenFullScreen: React.FunctionComponent = () => {
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
const result = useFullScreenURLs();
if (!result) {
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
}
const readWriteUrl = `https://cosmos.azure.com/?key=${result.readWrite}`;
const readUrl = `https://cosmos.azure.com/?key=${result.read}`;
return ( return (
<> <>
<div style={{ padding: "34px" }}>
<Stack tokens={{ childrenGap: 10 }}> <Stack tokens={{ childrenGap: 10 }}>
<Text> <Text>
Open this database account in a new browser tab with Cosmos DB Explorer. You can connect using your Open this database account in a new browser tab with Cosmos DB Explorer. Or copy the read-write or read only
Microsoft account or a connection string. access urls below to share with others. For security purposes, the URLs grant time-bound access to the
account. When access expires, you can reconnect, using a valid connection string for the account.
</Text> </Text>
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
<Stack horizontal tokens={{ childrenGap: 10 }}> <Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
onClick={() => {
copyToClipboard(readWriteUrl);
setIsReadWriteUrlCopy(true);
}}
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
iconProps={{ iconName: "Copy" }}
/>
<PrimaryButton <PrimaryButton
onClick={() => { onClick={() => {
window.open("https://cosmos.azure.com/", "_blank"); window.open(readWriteUrl, "_blank");
}}
text="Open"
iconProps={{ iconName: "OpenInNewWindow" }}
/>
</Stack>
<TextField label="Read Only" readOnly defaultValue={readUrl} />
<Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
ariaLabel={isReadUrlCopy ? "Copied url" : "Copy"}
onClick={() => {
setIsReadUrlCopy(true);
copyToClipboard(readUrl);
}}
text={isReadUrlCopy ? "Copied" : "Copy"}
iconProps={{ iconName: "Copy" }}
/>
<PrimaryButton
onClick={() => {
window.open(readUrl, "_blank");
}} }}
text="Open" text="Open"
iconProps={{ iconName: "OpenInNewWindow" }} iconProps={{ iconName: "OpenInNewWindow" }}
/> />
</Stack> </Stack>
</Stack> </Stack>
</div>
</> </>
); );
}; };

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,9 @@ 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("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"), useHashV2: false,
useHashV1: false,
enableAnalyticalStore: false, enableAnalyticalStore: false,
uniqueKeys: [], uniqueKeys: [],
errorMessage: "", errorMessage: "",
@@ -262,19 +259,11 @@ 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}
@@ -301,7 +290,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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 +335,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 +383,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 +466,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 +513,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 +545,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 +571,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 +602,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,18 +669,11 @@ 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}
@@ -835,7 +703,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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() && (
@@ -868,7 +735,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}} }}
> >
<Stack className="panelGroupSpacing" id="collapsibleSectionContent"> <Stack className="panelGroupSpacing" id="collapsibleSectionContent">
{isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && ( {isCapabilityEnabled("EnableMongo") && (
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
@@ -879,12 +746,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 +766,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
{userContext.apiType === "SQL" && ( {userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing">
<Checkbox <Checkbox
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)" label="My partition key is larger than 101 bytes"
checked={this.state.useHashV1} checked={this.state.useHashV2}
styles={{ styles={{
text: { fontSize: 12 }, text: { fontSize: 12 },
checkbox: { width: 12, height: 12 }, checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center", wordWrap: "break-word", whiteSpace: "break-spaces" }, label: { padding: 0, alignItems: "center" },
}} }}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ useHashV1: isChecked, subPartitionKeys: [] }) this.setState({ useHashV2: isChecked })
} }
/> />
<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 +832,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 +959,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 +1163,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

@@ -24,7 +24,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,

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

@@ -14,7 +14,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,

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}
@@ -41,9 +40,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
verticalAlign="center" verticalAlign="center"
>
<div
role="radiogroup"
> >
<input <input
aria-checked={true} aria-checked={true}
@@ -78,7 +74,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
> >
Use existing Use existing
</span> </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}
@@ -346,9 +312,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
verticalAlign="center" verticalAlign="center"
>
<div
role="radiogroup"
> >
<input <input
aria-checked={false} aria-checked={false}
@@ -386,7 +349,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
> >
Off Off
</span> </span>
</div>
</Stack> </Stack>
<Stack <Stack
className="panelGroupSpacing" className="panelGroupSpacing"
@@ -433,13 +395,10 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
<Stack <Stack
className="panelGroupSpacing" className="panelGroupSpacing"
id="collapsibleSectionContent" id="collapsibleSectionContent"
>
<Stack
className="panelGroupSpacing"
> >
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)" label="My partition key is larger than 101 bytes"
onChange={[Function]} onChange={[Function]}
styles={ styles={
Object { Object {
@@ -450,8 +409,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
"label": Object { "label": Object {
"alignItems": "center", "alignItems": "center",
"padding": 0, "padding": 0,
"whiteSpace": "break-spaces",
"wordWrap": "break-word",
}, },
"text": Object { "text": Object {
"fontSize": 12, "fontSize": 12,
@@ -459,24 +416,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
} }
} }
/> />
<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,105 +0,0 @@
export const newTableCommand = `DROP SCHEMA IF EXISTS cosmosdb_tutorial CASCADE;
CREATE SCHEMA cosmosdb_tutorial;
SET search_path to cosmosdb_tutorial;
CREATE TABLE github_users
(
user_id bigint,
url text,
login text,
avatar_url text,
gravatar_id text,
display_login text
);
CREATE TABLE github_events
(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
user_id bigint,
org jsonb,
created_at timestamp
);
CREATE INDEX event_type_index ON github_events (event_type);
CREATE INDEX payload_index ON github_events USING GIN (payload jsonb_path_ops);
`;
export const newTableCommandForDisplay = `DROP SCHEMA IF EXISTS cosmosdb_tutorial CASCADE;
CREATE SCHEMA cosmosdb_tutorial;
-- Using schema created for tutorial
SET search_path to cosmosdb_tutorial;
CREATE TABLE github_users
(
user_id bigint,
url text,
login text,
avatar_url text,
gravatar_id text,
display_login text
);
CREATE TABLE github_events
(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
user_id bigint,
org jsonb,
created_at timestamp
);
--Create indexes on events table
CREATE INDEX event_type_index ON github_events (event_type);
CREATE INDEX payload_index ON github_events USING GIN (payload jsonb_path_ops);`;
export const distributeTableCommand = `SET search_path to cosmosdb_tutorial;
SELECT create_distributed_table('github_users', 'user_id');
SELECT create_distributed_table('github_events', 'user_id');
`;
export const distributeTableCommandForDisplay = `-- Using schema created for the tutorial
SET search_path to cosmosdb_tutorial;
SELECT create_distributed_table('github_users', 'user_id');
SELECT create_distributed_table('github_events', 'user_id');`;
export const loadDataCommand = `SET search_path to cosmosdb_tutorial;
\\COPY github_users FROM PROGRAM 'wget -q -O - "$@" "https://examples.citusdata.com/users.csv"' WITH (FORMAT CSV);
\\COPY github_events FROM PROGRAM 'wget -q -O - "$@" "https://examples.citusdata.com/events.csv"' WITH (FORMAT CSV);
`;
export const loadDataCommandForDisplay = `-- Using schema created for the tutorial
SET search_path to cosmosdb_tutorial;
-- download users and store in table
\\COPY github_users FROM PROGRAM 'wget -q -O - "$@" "https://examples.citusdata.com/users.csv"' WITH (FORMAT CSV);
\\COPY github_events FROM PROGRAM 'wget -q -O - "$@" "https://examples.citusdata.com/events.csv"' WITH (FORMAT CSV);`;
export const queryCommand = `SET search_path to cosmosdb_tutorial;
SELECT count(*) FROM github_users;
SELECT created_at, event_type, repo->>'name' AS repo_name
FROM github_events
WHERE user_id = 3861633;
SELECT date_trunc('hour', created_at) AS hour, sum((payload->>'distinct_size')::int) AS num_commits FROM github_events WHERE event_type = 'PushEvent' AND payload @> '{"ref":"refs/heads/master"}' GROUP BY hour ORDER BY hour;
`;
export const queryCommandForDisplay = `-- Using schema created for the tutorial
SET search_path to cosmosdb_tutorial;
-- count all rows (across shards)
SELECT count(*) FROM github_users;
-- Find all events for a single user.
SELECT created_at, event_type, repo->>'name' AS repo_name
FROM github_events
WHERE user_id = 3861633;
-- Find the number of commits on the master branch per hour
SELECT date_trunc('hour', created_at) AS hour, sum((payload->>'distinct_size')::int) AS num_commits FROM github_events WHERE event_type = 'PushEvent' AND payload @> '{"ref":"refs/heads/master"}' GROUP BY hour ORDER BY hour;`;

View File

@@ -94,7 +94,7 @@ const getDescriptionText = (page: number): string => {
case 1: case 1:
return "Azure Cosmos DB is a fully managed NoSQL database service for modern app development. "; return "Azure Cosmos DB is a fully managed NoSQL database service for modern app development. ";
case 2: case 2:
return "Launch the quickstart for a tutorial to learn how to create a database, add sample data, connect to a sample app and more."; return "Launch the quickstart for a tutotrial to learn how to create a database, add sample data, connect to a sample app and more.";
case 3: case 3:
return "Already have an existing app? Connect your database to an app, or tooling of your choice from Data Explorer."; return "Already have an existing app? Connect your database to an app, or tooling of your choice from Data Explorer.";
default: default:

View File

@@ -1,22 +0,0 @@
import { Image, PrimaryButton, Stack, Text } from "@fluentui/react";
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/ExplorerContracts";
import React from "react";
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
export const QuickstartFirewallNotification: React.FC = (): JSX.Element => (
<Stack style={{ padding: "16px 20px" }}>
<Text block>
To use the PostgreSQL shell, you need to add a firewall rule to allow access from all IP addresses
(0.0.0.0-255.255.255).
</Text>
<Text block>We strongly recommend removing this rule once you finish using the PostgreSQL shell.</Text>
<Image style={{ margin: "20px 0" }} src={FirewallRuleScreenshot} />
<PrimaryButton
style={{ width: 150 }}
onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}
>
Add firewall rule
</PrimaryButton>
</Stack>
);

View File

@@ -11,17 +11,6 @@ import {
Text, Text,
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import {
distributeTableCommand,
distributeTableCommandForDisplay,
loadDataCommand,
loadDataCommandForDisplay,
newTableCommand,
newTableCommandForDisplay,
queryCommand,
queryCommandForDisplay,
} from "Explorer/Quickstart/PostgreQuickstartCommands";
import { useTerminal } from "hooks/useTerminal";
import React, { useState } from "react"; import React, { useState } from "react";
import Youtube from "react-youtube"; import Youtube from "react-youtube";
import Pivot1SelectedIcon from "../../../images/Pivot1_selected.svg"; import Pivot1SelectedIcon from "../../../images/Pivot1_selected.svg";
@@ -46,6 +35,24 @@ enum GuideSteps {
export const QuickstartGuide: React.FC = (): JSX.Element => { export const QuickstartGuide: React.FC = (): JSX.Element => {
const [currentStep, setCurrentStep] = useState<number>(0); const [currentStep, setCurrentStep] = useState<number>(0);
const newTableCommand = `CREATE TABLE github_users
(
user_id bigint,
url text,
login text,
.....`;
const distributeTableCommand = `SELECT create_distributed_table('github_users', 'user_id');
SELECT create_distributed_table('github_events', 'user_id');`;
const loadDataCommand = `-- download users and store in table
COPY github_users FROM PROGRAM 'curl https://examples.citusdata.com/
users.csv' WITH (FORMAT CSV)`;
const queryCommand = `-- Find all events for a single user.
-- (A common transactional/operational query)
SELECT created_at, event_type, repo->>'name' AS repo_name
FROM github_events
WHERE user_id = 3861633;`;
const onCopyBtnClicked = (selector: string): void => { const onCopyBtnClicked = (selector: string): void => {
const textfield: HTMLInputElement = document.querySelector(selector); const textfield: HTMLInputElement = document.querySelector(selector);
@@ -95,32 +102,24 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
<Stack style={{ paddingTop: 8, height: "100%", width: "100%" }}> <Stack style={{ paddingTop: 8, height: "100%", width: "100%" }}>
<Stack style={{ flexGrow: 1, padding: "0 20px", overflow: "auto" }}> <Stack style={{ flexGrow: 1, padding: "0 20px", overflow: "auto" }}>
<Text variant="xxLarge">Quick start guide</Text> <Text variant="xxLarge">Quick start guide</Text>
<Text variant="medium">Gettings started in Cosmos DB</Text>
{currentStep < 5 && ( {currentStep < 5 && (
<Pivot <Pivot style={{ marginTop: 10, width: "100%" }} selectedKey={GuideSteps[currentStep]}>
style={{ marginTop: 10, width: "100%" }}
selectedKey={GuideSteps[currentStep]}
onLinkClick={(item?: PivotItem) => setCurrentStep(Object.values(GuideSteps).indexOf(item.props.itemKey))}
>
<PivotItem <PivotItem
headerText="Login" headerText="Login"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)} onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)}
itemKey={GuideSteps[0]} itemKey={GuideSteps[0]}
onClick={() => { onClick={() => setCurrentStep(0)}
setCurrentStep(0);
}}
> >
<Stack style={{ marginTop: 20 }}> <Stack style={{ marginTop: 20 }}>
<Text> <Text>
This tutorial guides you to create and query distributed tables using a sample dataset. This tutorial walks you through the most essential Cosmos DB PostgreSQL statements that will be used
<br /> in the PostgreSQL shell (on the right). You can also choose to go through this quick start by
<br /> connecting to PGAdmin or other tooling of your choice. <br />
To begin, please enter the cluster&apos;s password in the PostgreSQL terminal. <br /> Before you can interact with your data using PGShell, you will need to login - please follow
<br /> instructions on the right to enter your password
<br />
Note: If you navigate out of the Quick Start tab (PostgreSQL Shell), the session will be closed and
all ongoing commands might be interrupted.
</Text> </Text>
<Youtube videoId="nT64dFSfiUo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} /> <Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "80%" }} />
</Stack> </Stack>
</PivotItem> </PivotItem>
<PivotItem <PivotItem
@@ -130,28 +129,19 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => setCurrentStep(1)} onClick={() => setCurrentStep(1)}
> >
<Stack style={{ marginTop: 20 }}> <Stack style={{ marginTop: 20 }}>
<Text>Let&apos;s create two tables github_users and github_events in cosmosdb_tutorial schema.</Text> <Text>
<DefaultButton After logging in, lets create some new tables for storing data. We will start with two sample tables
style={{ marginTop: 16, width: 150 }} - one for storing github users and one for storing github events
onClick={() => useTerminal.getState().sendMessage(newTableCommand)} </Text>
> <DefaultButton style={{ marginTop: 16, width: 110 }}>New table</DefaultButton>
Create new table
</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}> <Stack horizontal style={{ marginTop: 16 }}>
<TextField <TextField
id="newTableCommand" id="newTableCommand"
multiline multiline
rows={5} rows={6}
readOnly readOnly
defaultValue={newTableCommandForDisplay} defaultValue={newTableCommand}
styles={{ styles={{ root: { width: "90%" } }}
root: { width: "90%" },
field: {
backgroundColor: "#EEEEEE",
fontFamily:
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
},
}}
/> />
<IconButton <IconButton
iconProps={{ iconProps={{
@@ -160,7 +150,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#newTableCommand")} onClick={() => onCopyBtnClicked("#newTableCommand")}
/> />
</Stack> </Stack>
<Youtube videoId="il_sA6U1WcY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} /> <Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack> </Stack>
</PivotItem> </PivotItem>
<PivotItem <PivotItem
@@ -171,32 +161,21 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
> >
<Stack style={{ marginTop: 20 }}> <Stack style={{ marginTop: 20 }}>
<Text> <Text>
Lets distribute the two tables using the create_distributed_table() function. Congratulations, you have now created your first 2 tables.
<br /> <br />
<br /> <br />
We are choosing user_id as the distribution column for our sample dataset. Your table needs to be sharded on the worker nodes. You need to distribute table before you load any
data or run any queries
</Text> </Text>
<DefaultButton <DefaultButton style={{ marginTop: 16, width: 150 }}>Distribute table</DefaultButton>
style={{ marginTop: 16, width: 200 }}
onClick={() => useTerminal.getState().sendMessage(distributeTableCommand)}
>
Create distributed table
</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}> <Stack horizontal style={{ marginTop: 16 }}>
<TextField <TextField
id="distributeTableCommand" id="distributeTableCommand"
multiline multiline
rows={5} rows={2}
readOnly readOnly
defaultValue={distributeTableCommandForDisplay} defaultValue={distributeTableCommand}
styles={{ styles={{ root: { width: "90%" } }}
root: { width: "90%" },
field: {
backgroundColor: "#EEEEEE",
fontFamily:
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
},
}}
/> />
<IconButton <IconButton
iconProps={{ iconProps={{
@@ -205,7 +184,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#distributeTableCommand")} onClick={() => onCopyBtnClicked("#distributeTableCommand")}
/> />
</Stack> </Stack>
<Youtube videoId="kCCDRRrN1r0" style={{ margin: "20px 0" }} opts={{ width: "90%" }} /> <Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack> </Stack>
</PivotItem> </PivotItem>
<PivotItem <PivotItem
@@ -215,28 +194,22 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => setCurrentStep(3)} onClick={() => setCurrentStep(3)}
> >
<Stack style={{ marginTop: 20 }}> <Stack style={{ marginTop: 20 }}>
<Text>Let&apos;s load the two tables with a sample dataset generated from the GitHub API.</Text> <Text>
<DefaultButton We&apos;re ready to fill the tables with sample data.
style={{ marginTop: 16, width: 110 }} <br />
onClick={() => useTerminal.getState().sendMessage(loadDataCommand)} <br />
> For this quick start, we&apos;ll use a dataset previously captured from the GitHub API. Run the
Load data command below to load the data
</DefaultButton> </Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>Load data</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}> <Stack horizontal style={{ marginTop: 16 }}>
<TextField <TextField
id="loadDataCommand" id="loadDataCommand"
multiline multiline
rows={5} rows={4}
readOnly readOnly
defaultValue={loadDataCommandForDisplay} defaultValue={loadDataCommand}
styles={{ styles={{ root: { width: "90%" } }}
root: { width: "90%" },
field: {
backgroundColor: "#EEEEEE",
fontFamily:
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
},
}}
/> />
<IconButton <IconButton
iconProps={{ iconProps={{
@@ -245,7 +218,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#loadDataCommand")} onClick={() => onCopyBtnClicked("#loadDataCommand")}
/> />
</Stack> </Stack>
<Youtube videoId="XSMEE2tujEk" style={{ margin: "20px 0" }} opts={{ width: "90%" }} /> <Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack> </Stack>
</PivotItem> </PivotItem>
<PivotItem <PivotItem
@@ -256,29 +229,19 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
> >
<Stack style={{ marginTop: 20 }}> <Stack style={{ marginTop: 20 }}>
<Text> <Text>
Congratulations on creating and distributing your tables. Now, it&apos;s time to run your first query! github_users is a distributed table, meaning its data is divided between multiple shards. Hyperscale
(Citus) automatically runs the count on all shards in parallel, and combines the results. Lets try a
query.
</Text> </Text>
<DefaultButton <DefaultButton style={{ marginTop: 16, width: 110 }}>Try query</DefaultButton>
style={{ marginTop: 16, width: 115 }}
onClick={() => useTerminal.getState().sendMessage(queryCommand)}
>
Try queries
</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}> <Stack horizontal style={{ marginTop: 16 }}>
<TextField <TextField
id="queryCommand" id="queryCommand"
multiline multiline
rows={5} rows={6}
readOnly readOnly
defaultValue={queryCommandForDisplay} defaultValue={queryCommand}
styles={{ styles={{ root: { width: "90%" } }}
root: { width: "90%" },
field: {
backgroundColor: "#EEEEEE",
fontFamily:
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
},
}}
/> />
<IconButton <IconButton
iconProps={{ iconProps={{
@@ -287,7 +250,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#queryCommand")} onClick={() => onCopyBtnClicked("#queryCommand")}
/> />
</Stack> </Stack>
<Youtube videoId="k_EanjMtaPg" style={{ margin: "20px 0" }} opts={{ width: "90%" }} /> <Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack> </Stack>
</PivotItem> </PivotItem>
</Pivot> </Pivot>
@@ -295,7 +258,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
{currentStep === 5 && ( {currentStep === 5 && (
<Stack style={{ margin: "auto" }} horizontalAlign="center"> <Stack style={{ margin: "auto" }} horizontalAlign="center">
<Image src={CompleteIcon} /> <Image src={CompleteIcon} />
<Text variant="mediumPlus" style={{ fontWeight: 900, marginTop: 7 }}> <Text variant="mediumPlus" style={{ fontWeight: 600, marginTop: 7 }}>
You are all set! You are all set!
</Text> </Text>
<Text variant="mediumPlus" style={{ marginTop: 8 }}> <Text variant="mediumPlus" style={{ marginTop: 8 }}>

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,9 +99,9 @@ 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 API 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.
</TeachingBubble> </TeachingBubble>
); );
@@ -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

@@ -14,6 +14,10 @@
padding-right: 16px; padding-right: 16px;
max-width: 1168px; max-width: 1168px;
> * {
justify-content: space-between;
}
> .title { > .title {
position: relative; // To attach FeaturePanelLauncher as absolute position: relative; // To attach FeaturePanelLauncher as absolute
color: @BaseHigh; color: @BaseHigh;
@@ -36,7 +40,6 @@
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
margin: 40px auto; margin: 40px auto;
width: 84%;
> .mainButton { > .mainButton {
min-width: 124px; min-width: 124px;

View File

@@ -11,8 +11,6 @@ import {
TeachingBubbleContent, TeachingBubbleContent,
Text, Text,
} from "@fluentui/react"; } from "@fluentui/react";
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { TerminalKind } from "Contracts/ViewModels"; import { TerminalKind } from "Contracts/ViewModels";
import { useCarousel } from "hooks/useCarousel"; import { useCarousel } from "hooks/useCarousel";
import { usePostgres } from "hooks/usePostgres"; import { usePostgres } from "hooks/usePostgres";
@@ -93,12 +91,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
() => this.setState({}), () => this.setState({}),
(state) => state.showPostgreTeachingBubble (state) => state.showPostgreTeachingBubble
), ),
},
{
dispose: usePostgres.subscribe(
() => this.setState({}),
(state) => state.showResetPasswordBubble
),
} }
); );
} }
@@ -116,17 +108,8 @@ 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" {userContext.apiType === "Postgres" ? "Welcome to Cosmos DB - PostgreSQL" : "Welcome to Cosmos DB"}
aria-label={
userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"
}
>
{userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"}
<FeaturePanelLauncher /> <FeaturePanelLauncher />
</div> </div>
<div className="subtitle"> <div className="subtitle">
@@ -135,21 +118,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
: "Globally distributed, multi-model database service for any scale"} : "Globally distributed, multi-model database service for any scale"}
</div> </div>
<div className="mainButtonsContainer"> <div className="mainButtonsContainer">
{userContext.apiType === "Postgres" && {userContext.apiType === "Postgres" && usePostgres.getState().showPostgreTeachingBubble && (
usePostgres.getState().showPostgreTeachingBubble &&
!usePostgres.getState().showResetPasswordBubble && (
<TeachingBubble <TeachingBubble
headline="New to Cosmos DB PGSQL?" headline="New to Cosmos DB PGSQL?"
target={"#mainButton-quickstartDescription"} target={"#quickstartDescription"}
hasCloseButton hasCloseButton
onDismiss={() => usePostgres.getState().setShowPostgreTeachingBubble(false)} onDismiss={() => usePostgres.getState().setShowPostgreTeachingBubble(false)}
calloutProps={{
directionalHint: DirectionalHint.rightCenter,
directionalHintFixed: true,
preventDismissOnLostFocus: true,
preventDismissOnResize: true,
preventDismissOnScroll: true,
}}
primaryButtonProps={{ primaryButtonProps={{
text: "Get started", text: "Get started",
onClick: () => { onClick: () => {
@@ -158,13 +132,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}, },
}} }}
> >
Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you can
can find sample data, query. find sample data, query.
</TeachingBubble> </TeachingBubble>
)} )}
{mainItems.map((item) => ( {mainItems.map((item) => (
<Stack <Stack
id={`mainButton-${item.id}`}
horizontal horizontal
className="mainButton focusable" className="mainButton focusable"
key={`${item.title}`} key={`${item.title}`}
@@ -188,36 +161,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</div> </div>
</Stack> </Stack>
))} ))}
{userContext.apiType === "Postgres" && usePostgres.getState().showResetPasswordBubble && (
<TeachingBubble
headline="Create your password"
target={"#mainButton-quickstartDescription"}
hasCloseButton
onDismiss={() => {
localStorage.setItem(userContext.databaseAccount.id, "true");
usePostgres.getState().setShowResetPasswordBubble(false);
}}
calloutProps={{
directionalHint: DirectionalHint.bottomRightEdge,
directionalHintFixed: true,
preventDismissOnLostFocus: true,
preventDismissOnResize: true,
preventDismissOnScroll: true,
}}
primaryButtonProps={{
text: "Create",
onClick: () => {
localStorage.setItem(userContext.databaseAccount.id, "true");
sendMessage({
type: MessageTypes.OpenPostgreSQLPasswordReset,
});
usePostgres.getState().setShowResetPasswordBubble(false);
},
}}
>
If you haven&apos;t changed your password yet, change it now.
</TeachingBubble>
)}
</div> </div>
{useCarousel.getState().showCoachMark && ( {useCarousel.getState().showCoachMark && (
<Coachmark <Coachmark
@@ -248,8 +191,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</Coachmark> </Coachmark>
)} )}
{userContext.apiType === "Postgres" ? ( {userContext.apiType === "Postgres" ? (
<Stack horizontal style={{ margin: "0 auto", width: "84%" }} tokens={{ childrenGap: 32 }}> <Stack horizontal style={{ margin: "0 auto" }} tokens={{ childrenGap: "15%" }}>
<Stack style={{ width: "33%" }}> <Stack>
<Text <Text
variant="large" variant="large"
style={{ style={{
@@ -261,7 +204,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</Text> </Text>
{this.getNextStepItems()} {this.getNextStepItems()}
</Stack> </Stack>
<Stack style={{ width: "33%" }}> <Stack>
<Text <Text
variant="large" variant="large"
style={{ style={{
@@ -273,7 +216,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</Text> </Text>
{this.getTipsAndLearnMoreItems()} {this.getTipsAndLearnMoreItems()}
</Stack> </Stack>
<Stack style={{ width: "33%" }}></Stack>
</Stack> </Stack>
) : ( ) : (
<div className="moreStuffContainer"> <div className="moreStuffContainer">
@@ -311,11 +253,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
public createMainItems(): SplashScreenItem[] { public createMainItems(): SplashScreenItem[] {
const heroes: SplashScreenItem[] = []; const heroes: SplashScreenItem[] = [];
if ( if (userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Postgres") {
userContext.apiType === "SQL" ||
userContext.apiType === "Mongo" ||
(userContext.apiType === "Postgres" && !userContext.isReplica)
) {
const launchQuickstartBtn = { const launchQuickstartBtn = {
id: "quickstartDescription", id: "quickstartDescription",
iconSrc: QuickStartIcon, iconSrc: QuickStartIcon,
@@ -344,7 +282,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: PowerShellIcon, iconSrc: PowerShellIcon,
title: "PostgreSQL Shell", title: "PostgreSQL Shell",
description: "Create table and interact with data using PostgreSQLs shell interface", description: "Create table and interact with data using PostgreSQLs shell interface",
onClick: () => this.container.openNotebookTerminal(TerminalKind.Postgres), onClick: () => this.container.openNotebookTerminal(TerminalKind.Mongo),
}; };
heroes.push(postgreShellBtn); heroes.push(postgreShellBtn);
} else { } else {
@@ -362,11 +300,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
const connectBtn = { const connectBtn = {
iconSrc: ConnectIcon, iconSrc: ConnectIcon,
title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect", title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
description: description:
userContext.apiType === "Postgres" userContext.apiType === "Postgres"
? "Prefer pgAdmin? Find your connection strings here" ? "Prefer using your own choice of tooling? Find the connection string you need to connect"
: "Prefer using your own choice of tooling? Find the connection string you need to connect", : "Prefer PGadmin? Find your connection strings here",
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect), onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
}; };
heroes.push(connectBtn); heroes.push(connectBtn);
@@ -450,7 +388,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
{ {
link: "https://aka.ms/mongodbintro", link: "https://aka.ms/mongodbintro",
title: "What is the MongoDB API?", title: "What is the MongoDB API?",
description: "Understand Azure Cosmos DB for MongoDB and its features.", description: "Understand the Cosmos DB API for MongoDB and its features.",
}, },
{ {
link: "https://aka.ms/mongodbfeaturesupport", link: "https://aka.ms/mongodbfeaturesupport",
@@ -507,7 +445,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
{ {
link: "https://aka.ms/tableintro", link: "https://aka.ms/tableintro",
title: "What is the Table API?", title: "What is the Table API?",
description: "Understand Azure Cosmos DB for Table and its features", description: "Understand the Table API in Cosmos DB and its features",
}, },
{ {
link: "https://aka.ms/tableimport", link: "https://aka.ms/tableimport",
@@ -516,8 +454,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}, },
{ {
link: "https://aka.ms/tablefaq", link: "https://aka.ms/tablefaq",
title: "Azure Cosmos DB for Table FAQs", title: "Table API FAQs",
description: "Common questions about Azure Cosmos DB for Table", description: "Common questions about the Table API",
}, },
]; ];
break; break;
@@ -535,7 +473,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 +508,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 +518,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 +542,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 +556,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 +580,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":
@@ -648,14 +592,18 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
{ {
link: "https://aka.ms/tabledotnet", link: "https://aka.ms/tabledotnet",
title: "Build a .NET App", title: "Build a .NET App",
description: "How to access Azure Cosmos DB for Table from a .NET app.", description: "How to access Table API from a .NET app.",
}, },
{ {
link: "https://aka.ms/Tablejava", link: "https://aka.ms/Tablejava",
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 Table API app with Java SDK ",
},
{
link: "https://aka.ms/tablenodejs",
title: "Build a Node.js App",
description: "Create a Table API app with Node.js SDK",
}, },
cdbLiveTv,
]; ];
break; break;
} }
@@ -686,24 +634,24 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
private getNextStepItems(): JSX.Element { private getNextStepItems(): JSX.Element {
const items: { link: string; title: string; description: string }[] = [ const items: { link: string; title: string; description: string }[] = [
{ {
link: "https://go.microsoft.com/fwlink/?linkid=2208312", link: "",
title: "Data Modeling", title: "Performance tuning",
description: "", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
}, },
{ {
link: " https://go.microsoft.com/fwlink/?linkid=2206941 ", link: "",
title: "How to choose a Distribution Column", title: "Join Citus community",
description: "", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
}, },
{ {
link: "https://go.microsoft.com/fwlink/?linkid=2207425", link: "",
title: "Build Apps with Python/Java/Django", title: "Useful diagnostic queries",
description: "", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
}, },
]; ];
return ( return (
<Stack style={{ minWidth: 124, maxWidth: 296 }}> <Stack>
{items.map((item, i) => ( {items.map((item, i) => (
<Stack key={`nextStep${i}`} style={{ marginBottom: 26 }}> <Stack key={`nextStep${i}`} style={{ marginBottom: 26 }}>
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}> <Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
@@ -722,24 +670,24 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
private getTipsAndLearnMoreItems(): JSX.Element { private getTipsAndLearnMoreItems(): JSX.Element {
const items: { link: string; title: string; description: string }[] = [ const items: { link: string; title: string; description: string }[] = [
{ {
link: "https://go.microsoft.com/fwlink/?linkid=2207226", link: "",
title: "Performance Tuning", title: "Data modeling",
description: "", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
}, },
{ {
link: "https://go.microsoft.com/fwlink/?linkid=2208037", link: "",
title: "Useful Diagnostic Queries", title: "How to choose a distribution Column",
description: "", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
}, },
{ {
link: "https://go.microsoft.com/fwlink/?linkid=2205270", link: "",
title: "Distributed SQL Reference", title: "Build apps with Python/ Java/ Django",
description: "", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
}, },
]; ];
return ( return (
<Stack style={{ minWidth: 124, maxWidth: 296 }}> <Stack>
{items.map((item, i) => ( {items.map((item, i) => (
<Stack key={`tips${i}`} style={{ marginBottom: 26 }}> <Stack key={`tips${i}`} style={{ marginBottom: 26 }}>
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}> <Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>

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

@@ -1,145 +0,0 @@
import {
Checkbox,
Dropdown,
Icon,
IconButton,
IDropdownOption,
ITextFieldStyles,
Label,
Link,
Stack,
Text,
TextField,
TooltipHost,
} from "@fluentui/react";
import React from "react";
import { userContext } from "UserContext";
export const PostgresConnectTab: React.FC = (): JSX.Element => {
const { adminLogin, nodes, enablePublicIpAccess } = userContext.postgresConnectionStrParams;
const [usePgBouncerPort, setUsePgBouncerPort] = React.useState<boolean>(false);
const [selectedNode, setSelectedNode] = React.useState<string>(nodes?.[0]?.value);
const portNumber = usePgBouncerPort ? "6432" : "5432";
const onCopyBtnClicked = (selector: string): void => {
const textfield: HTMLInputElement = document.querySelector(selector);
textfield.select();
document.execCommand("copy");
};
const textfieldStyles: Partial<ITextFieldStyles> = {
root: { width: "100%" },
field: { backgroundColor: "rgb(230, 230, 230)" },
fieldGroup: { borderColor: "rgb(138, 136, 134)" },
subComponentStyles: { label: { fontWeight: 400 } },
description: { fontWeight: 400 },
};
const nodesDropdownOptions: IDropdownOption[] = nodes.map((node) => ({
key: node.value,
text: node.text,
}));
const postgresSQLConnectionURL = `postgres://${adminLogin}:{your_password}@${selectedNode}:${portNumber}/citus?sslmode=require`;
const psql = `psql "host=${selectedNode} port=${portNumber} dbname=citus user=${adminLogin} password={your_password} sslmode=require"`;
const jdbc = `jdbc:postgresql://${selectedNode}:${portNumber}/citus?user=${adminLogin}&password={your_password}&sslmode=require`;
const libpq = `host=${selectedNode} port=${portNumber} dbname=citus user=${adminLogin} password={your_password} sslmode=require`;
const adoDotNet = `Server=${selectedNode};Database=citus;Port=${portNumber};User Id=${adminLogin};Password={your_password};Ssl Mode=Require;`;
return (
<div style={{ width: "100%", padding: 16 }}>
<Stack horizontal verticalAlign="center" style={{ marginBottom: 8 }}>
<Label style={{ marginRight: 8 }}>Public IP addresses on worker nodes:</Label>
<TooltipHost
content="
You can enable or disable public IP addresses on the worker nodes on 'Networking' page of your server group."
>
<Icon style={{ margin: "5px 8px 0 0", cursor: "default" }} iconName="Info" />
</TooltipHost>
<TextField value={enablePublicIpAccess ? "On" : "Off"} readOnly disabled />
</Stack>
<Stack horizontal style={{ marginBottom: 8 }}>
<Label style={{ marginRight: 85 }}>Show connection strings for</Label>
<Dropdown
options={nodesDropdownOptions}
selectedKey={selectedNode}
onChange={(_, option) => {
const selectedNode = option.key as string;
setSelectedNode(selectedNode);
if (!selectedNode.startsWith("c.")) {
setUsePgBouncerPort(false);
}
}}
style={{ width: 200 }}
/>
</Stack>
<Stack horizontal style={{ marginBottom: 8 }}>
<Label style={{ marginRight: 44 }}>PgBouncer connection strings</Label>
<Checkbox
boxSide="end"
checked={usePgBouncerPort}
onChange={(_, checked: boolean) => setUsePgBouncerPort(checked)}
disabled={!selectedNode?.startsWith("c.")}
/>
</Stack>
<Stack horizontal verticalAlign="end" style={{ marginBottom: 8 }}>
<TextField
label="PostgreSQL connection URL"
id="postgresSQLConnectionURL"
readOnly
value={postgresSQLConnectionURL}
styles={textfieldStyles}
/>
<IconButton iconProps={{ iconName: "Copy" }} onClick={() => onCopyBtnClicked("#postgresSQLConnectionURL")} />
</Stack>
<Stack horizontal verticalAlign="end" style={{ marginBottom: 8 }}>
<TextField label="psql" id="psql" readOnly value={psql} styles={textfieldStyles} />
<IconButton iconProps={{ iconName: "Copy" }} onClick={() => onCopyBtnClicked("#psql")} />
</Stack>
<Stack horizontal verticalAlign="end" style={{ marginBottom: 8 }}>
<TextField label="JDBC" id="JDBC" readOnly value={jdbc} styles={textfieldStyles} />
<IconButton iconProps={{ iconName: "Copy" }} onClick={() => onCopyBtnClicked("#JDBC")} />
</Stack>
<Stack horizontal verticalAlign="end" style={{ marginBottom: 8 }}>
<TextField
label="Node.js, Python, Ruby, PHP, C++ (libpq)"
id="libpq"
readOnly
value={libpq}
styles={textfieldStyles}
/>
<IconButton iconProps={{ iconName: "Copy" }} onClick={() => onCopyBtnClicked("#libpq")} />
</Stack>
<Stack horizontal verticalAlign="end" style={{ marginBottom: 8 }}>
<TextField label="ADO.NET" id="adoDotNet" readOnly value={adoDotNet} styles={textfieldStyles} />
<IconButton iconProps={{ iconName: "Copy" }} onClick={() => onCopyBtnClicked("#adoDotNet")} />
</Stack>
<Label>Secure connections</Label>
<Text style={{ marginBottom: 8 }}>
Only secure connections are supported. For production use cases, we recommend using the &apos;verify-full&apos;
mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and
provide it when connecting to the database.{" "}
<Link href="https://go.microsoft.com/fwlink/?linkid=2155061" target="_blank">
Learn more
</Link>
</Text>
<Label>Connect with pgAdmin</Label>
<Text>
Refer to our{" "}
<Link
href="https://learn.microsoft.com/en-us/azure/postgresql/hyperscale/howto-connect?tabs=pgadmin"
target="_blank"
>
guide
</Link>{" "}
to help you connect via pgAdmin.
</Text>
</div>
);
};

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 class="addclauseProperty-Img" style="margin-bottom: 5px" src="/Add-property.svg" /> <img
class="addclauseProperty-Img"
style="margin-bottom: 5px"
src="/Add-property.svg"
alt="Add new clause"
/>
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span> <span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
</span> </span>
</div> </div>
</div> </div>
</button>
</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>
</button> <span
<button class="entity-Add-Cancel"
role="button"
tabindex="0"
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}" 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

@@ -1,15 +1,11 @@
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react"; import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
import { configContext } from "ConfigContext"; import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
import { NotebookWorkspaceConnectionInfo, PostgresFirewallRule } from "Contracts/DataModels";
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent"; import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide"; import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { armRequest } from "Utils/arm/request";
interface QuickstartTabProps { interface QuickstartTabProps {
explorer: Explorer; explorer: Explorer;
@@ -17,42 +13,14 @@ interface QuickstartTabProps {
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => { export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo); const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
authToken: notebookServerInfo.authToken,
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
forwardingId: notebookServerInfo.forwardingId,
});
const checkFirewallRules = async (): Promise<void> => {
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response: any = await armRequest({
host: configContext.ARM_ENDPOINT,
path: firewallRulesUri,
method: "GET",
apiVersion: "2020-10-05-privatepreview",
});
const firewallRules: PostgresFirewallRule[] = response?.data?.value || response?.value || [];
const isEnabled = firewallRules.some(
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
);
setIsAllPublicIPAddressEnabled(isEnabled);
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
if (!isEnabled && useTabs.getState().activeReactTab === ReactTabKind.Quickstart) {
setTimeout(checkFirewallRules, 30000);
}
};
useEffect(() => {
checkFirewallRules();
});
useEffect(() => { useEffect(() => {
explorer.allocateContainer(); explorer.allocateContainer();
}, []); }, []);
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
authToken: notebookServerInfo.authToken,
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/mongo`,
forwardingId: notebookServerInfo.forwardingId,
});
return ( return (
<Stack style={{ width: "100%" }} horizontal> <Stack style={{ width: "100%" }} horizontal>
@@ -60,24 +28,15 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
<QuickstartGuide /> <QuickstartGuide />
</Stack> </Stack>
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}> <Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
{!isAllPublicIPAddressEnabled && <QuickstartFirewallNotification />} {notebookServerInfo?.notebookServerEndpoint && (
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
<NotebookTerminalComponent <NotebookTerminalComponent
notebookServerInfo={getNotebookServerInfo()} notebookServerInfo={getNotebookServerInfo()}
databaseAccount={userContext.databaseAccount} databaseAccount={userContext.databaseAccount}
tabId="QuickstartPSQLShell" tabId="EmbbedTerminal"
/> />
)} )}
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && ( {!notebookServerInfo?.notebookServerEndpoint && (
<Stack style={{ margin: "auto 0" }}> <Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
<Text block style={{ margin: "auto" }}>
Connecting to the PostgreSQL shell.
</Text>
<Text block style={{ margin: "auto" }}>
If the cluster was just created, this could take up to a minute.
</Text>
<Spinner styles={{ root: { marginTop: 16 } }} size={SpinnerSize.large}></Spinner>
</Stack>
)} )}
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -1,13 +1,8 @@
import { MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { CollectionTabKind } from "Contracts/ViewModels"; import { CollectionTabKind } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; 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 { 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";
@@ -24,23 +19,10 @@ 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 } = useTabs();
return ( return (
<div className="tabsManagerContainer"> <div className="tabsManagerContainer">
{networkSettingsWarning && (
<MessageBar
messageBarType={MessageBarType.warning}
actions={
<MessageBarButton onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}>
Change network settings
</MessageBarButton>
}
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
>
{networkSettingsWarning}
</MessageBar>
)}
<div id="content" className="flexContainer hideOverflows"> <div id="content" className="flexContainer hideOverflows">
<div className="nav-tabs-margin"> <div className="nav-tabs-margin">
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist"> <ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
@@ -92,7 +74,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,6 +83,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
ref={focusTab} ref={focusTab}
> >
<span className="tabNavContentContainer"> <span className="tabNavContentContainer">
<a data-toggle="tab" href={"#" + tabId} tabIndex={-1}>
<div className="tab_Content"> <div className="tab_Content">
<span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}> <span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}>
{useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />} {useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />}
@@ -116,6 +98,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
</span> </span>
)} )}
</div> </div>
</a>
</span> </span>
</li> </li>
); );
@@ -206,7 +189,7 @@ const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => { const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
switch (activeReactTab) { switch (activeReactTab) {
case ReactTabKind.Connect: case ReactTabKind.Connect:
return userContext.apiType === "Postgres" ? <PostgresConnectTab /> : <ConnectTab />; return <ConnectTab />;
case ReactTabKind.Home: case ReactTabKind.Home:
return <SplashScreen explorer={explorer} />; return <SplashScreen explorer={explorer} />;
case ReactTabKind.Quickstart: case ReactTabKind.Quickstart:

View File

@@ -1,9 +1,6 @@
import { Spinner, SpinnerSize } from "@fluentui/react"; import { Spinner, SpinnerSize } from "@fluentui/react";
import { configContext } from "ConfigContext";
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
import * as ko from "knockout"; import * as ko from "knockout";
import * as React from "react"; import * as React from "react";
import { armRequest } from "Utils/arm/request";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -29,15 +26,10 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
constructor( constructor(
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo, private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
private getDatabaseAccount: () => DataModels.DatabaseAccount, private getDatabaseAccount: () => DataModels.DatabaseAccount,
private getTabId: () => string, private getTabId: () => string
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>
) {} ) {}
public renderComponent(): JSX.Element { public renderComponent(): JSX.Element {
if (!this.isAllPublicIPAddressesEnabled()) {
return <QuickstartFirewallNotification />;
}
return this.parameters() ? ( return this.parameters() ? (
<NotebookTerminalComponent <NotebookTerminalComponent
notebookServerInfo={this.getNotebookServerInfo()} notebookServerInfo={this.getNotebookServerInfo()}
@@ -54,33 +46,25 @@ export default class TerminalTab extends TabsBase {
public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> '; public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> ';
private container: Explorer; private container: Explorer;
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter; private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
constructor(options: TerminalTabOptions) { constructor(options: TerminalTabOptions) {
super(options); super(options);
this.container = options.container; this.container = options.container;
this.isAllPublicIPAddressesEnabled = ko.observable(true);
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter( this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
() => this.getNotebookServerInfo(options), () => this.getNotebookServerInfo(options),
() => userContext?.databaseAccount, () => userContext?.databaseAccount,
() => this.tabId, () => this.tabId
this.isAllPublicIPAddressesEnabled
); );
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => { this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
if ( if (
this.isTemplateReady() && this.isTemplateReady() &&
useNotebook.getState().isNotebookEnabled && useNotebook.getState().isNotebookEnabled &&
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint && useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
this.isAllPublicIPAddressesEnabled()
) { ) {
return true; return true;
} }
return false; return false;
}); });
if (options.kind === ViewModels.TerminalKind.Postgres) {
this.checkPostgresFirewallRules();
}
} }
public getContainer(): Explorer { public getContainer(): Explorer {
@@ -111,10 +95,6 @@ export default class TerminalTab extends TabsBase {
endpointSuffix = "cassandra"; endpointSuffix = "cassandra";
break; break;
case ViewModels.TerminalKind.Postgres:
endpointSuffix = "postgresql";
break;
default: default:
throw new Error(`Terminal kind: ${options.kind} not supported`); throw new Error(`Terminal kind: ${options.kind} not supported`);
} }
@@ -126,25 +106,4 @@ export default class TerminalTab extends TabsBase {
forwardingId: info.forwardingId, forwardingId: info.forwardingId,
}; };
} }
private async checkPostgresFirewallRules(): Promise<void> {
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response: any = await armRequest({
host: configContext.ARM_ENDPOINT,
path: firewallRulesUri,
method: "GET",
apiVersion: "2020-10-05-privatepreview",
});
const firewallRules: DataModels.PostgresFirewallRule[] = response?.data?.value || response?.value || [];
const isEnabled = firewallRules.some(
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
);
this.isAllPublicIPAddressesEnabled(isEnabled);
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
if (!isEnabled) {
setTimeout(() => this.checkPostgresFirewallRules(), 30000);
}
}
} }

View File

@@ -1160,6 +1160,23 @@ export default class Collection implements ViewModels.Collection {
this.onDocumentDBDocumentsClick(); this.onDocumentDBDocumentsClick();
} }
/**
* Get correct collection label depending on account API
*/
public getLabel(): string {
if (userContext.apiType === "Tables") {
return "Entities";
} else if (userContext.apiType === "Cassandra") {
return "Rows";
} else if (userContext.apiType === "Gremlin") {
return "Graph";
} else if (userContext.apiType === "Mongo") {
return "Documents";
}
return "Items";
}
public getDatabase(): ViewModels.Database { public getDatabase(): ViewModels.Database {
return useDatabases.getState().findDatabaseWithId(this.databaseId); return useDatabases.getState().findDatabaseWithId(this.databaseId);
} }

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

@@ -1,6 +1,5 @@
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react"; import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { getItemName } from "Utils/APITypeUtils";
import shallow from "zustand/shallow"; import shallow from "zustand/shallow";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import DeleteIcon from "../../../images/delete.svg"; import DeleteIcon from "../../../images/delete.svg";
@@ -479,18 +478,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))
@@ -510,7 +497,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
const buildCollectionNode = (database: ViewModels.Database, collection: ViewModels.Collection): TreeNode => { const buildCollectionNode = (database: ViewModels.Database, collection: ViewModels.Collection): TreeNode => {
const children: TreeNode[] = []; const children: TreeNode[] = [];
children.push({ children.push({
label: getItemName(), label: collection.getLabel(),
id: collection.isSampleCollection ? "sampleItems" : "", id: collection.isSampleCollection ? "sampleItems" : "",
onClick: () => { onClick: () => {
collection.openTab(); collection.openTab();

View File

@@ -1,7 +1,6 @@
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react"; import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
import * as ko from "knockout"; import * as ko from "knockout";
import * as React from "react"; import * as React from "react";
import { getItemName } from "Utils/APITypeUtils";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import DeleteIcon from "../../../images/delete.svg"; import DeleteIcon from "../../../images/delete.svg";
import GalleryIcon from "../../../images/GalleryIcon.svg"; import GalleryIcon from "../../../images/GalleryIcon.svg";
@@ -255,7 +254,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode { private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode {
const children: TreeNode[] = []; const children: TreeNode[] = [];
children.push({ children.push({
label: getItemName(), label: collection.getLabel(),
onClick: () => { onClick: () => {
collection.openTab(); collection.openTab();
// push to most recent // push to most recent

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

@@ -32,7 +32,6 @@ import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
export class PhoenixClient { export class PhoenixClient {
private armResourceId: string;
private containerHealthHandler: NodeJS.Timeout; private containerHealthHandler: NodeJS.Timeout;
private retryOptions: promiseRetry.Options = { private retryOptions: promiseRetry.Options = {
retries: Notebook.retryAttempts, retries: Notebook.retryAttempts,
@@ -40,16 +39,8 @@ export class PhoenixClient {
minTimeout: Notebook.retryAttemptDelayMs, minTimeout: Notebook.retryAttemptDelayMs,
}; };
constructor(armResourceId: string) {
this.armResourceId = armResourceId;
}
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> { public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), { return this.executeContainerAssignmentOperation(provisionData, "allocate");
retries: 4,
maxTimeout: 20000,
minTimeout: 20000,
});
} }
public async resetContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> { public async resetContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
@@ -84,12 +75,9 @@ export class PhoenixClient {
} }
const phoenixError = responseJson as IPhoenixError; const phoenixError = responseJson as IPhoenixError;
if (response.status === HttpStatusCodes.Forbidden) { if (response.status === HttpStatusCodes.Forbidden) {
if (phoenixError.message === "Sequence contains no elements") { throw new Error(this.ConvertToForbiddenErrorString(phoenixError));
throw Error("Phoenix container allocation failed, please try again later.");
} }
throw new AbortError(this.ConvertToForbiddenErrorString(phoenixError)); throw new Error(phoenixError.message);
}
throw new AbortError(phoenixError.message);
} catch (error) { } catch (error) {
error.status = response?.status; error.status = response?.status;
throw error; throw error;
@@ -226,21 +214,22 @@ export class PhoenixClient {
} }
} }
private getPhoenixControlPlanePathPrefix(): string { public static getPhoenixEndpoint(): string {
if (!this.armResourceId) { const phoenixEndpoint =
throw new Error("The Phoenix client was not initialized properly: missing ARM resourcce id");
}
const toolsEndpoint =
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
if (!validateEndpoint(phoenixEndpoint, allowedJunoOrigins)) {
if (!validateEndpoint(toolsEndpoint, allowedJunoOrigins)) { const error = `${phoenixEndpoint} not allowed as juno endpoint`;
const error = `${toolsEndpoint} not allowed as tools endpoint`;
console.error(error); console.error(error);
throw new Error(error); throw new Error(error);
} }
return `${toolsEndpoint}/api/controlplane/toolscontainer/cosmosaccounts${this.armResourceId}`; return phoenixEndpoint;
}
public getPhoenixControlPlanePathPrefix(): string {
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts${
userContext.databaseAccount.id
}`;
} }
private static getHeaders(): HeadersInit { private static getHeaders(): HeadersInit {

View File

@@ -0,0 +1,31 @@
import * as Constants from "../../Common/Constants";
import { configContext } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { userContext } from "../../UserContext";
export default class AuthHeadersUtil {
public static async generateEncryptedToken(readOnly: boolean = false): Promise<DataModels.GenerateTokenResponse> {
const url = configContext.BACKEND_ENDPOINT + "/api/tokens/generateToken" + AuthHeadersUtil._generateResourceUrl();
const headers: any = { authorization: userContext.authorizationToken };
headers[Constants.HttpHeaders.getReadOnlyKey] = readOnly;
const response = await fetch(url, { method: "POST", headers });
const result = await response.json();
// This API has a quirk where the response must be parsed to JSON twice
return JSON.parse(result);
}
private static _generateResourceUrl(): string {
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const apiKind: DataModels.ApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.apiType);
const accountEndpoint = databaseAccount?.properties?.documentEndpoint || "";
const sid = subscriptionId || "";
const rg = resourceGroup || "";
const dba = databaseAccount?.name || "";
const resourceUrl = encodeURIComponent(accountEndpoint);
const rid = "";
const rtype = "";
return `?resourceUrl=${resourceUrl}&rid=${rid}&rtype=${rtype}&sid=${sid}&rg=${rg}&dba=${dba}&api=${apiKind}`;
}
}

View File

@@ -29,12 +29,7 @@ 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 enablePGQuickstart: 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 +91,7 @@ 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"), enablePGQuickstart: "true" === get("enablepgquickstart"),
enableLegacyMongoShellV1: "true" === get("enablelegacymongoshellv1"),
enableLegacyMongoShellV1Debug: "true" === get("enablelegacymongoshellv1debug"),
enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
}; };
} }

View File

@@ -40,23 +40,23 @@ export enum SelfServeType {
*/ */
export enum BladeType { export enum BladeType {
/** /**
* Keys blade of a Azure Cosmos DB for NoSQL account. * Keys blade of a SQL API account.
*/ */
SqlKeys = "keys", SqlKeys = "keys",
/** /**
* Keys blade of a Azure Cosmos DB for MongoDB account. * Keys blade of a Mongo API account.
*/ */
MongoKeys = "mongoDbKeys", MongoKeys = "mongoDbKeys",
/** /**
* Keys blade of a Azure Cosmos DB for Apache Cassandra account. * Keys blade of a Cassandra API account.
*/ */
CassandraKeys = "cassandraDbKeys", CassandraKeys = "cassandraDbKeys",
/** /**
* Keys blade of a Azure Cosmos DB for Apache Gremlin account. * Keys blade of a Gremlin API account.
*/ */
GremlinKeys = "keys", GremlinKeys = "keys",
/** /**
* Keys blade of a Azure Cosmos DB for Table account. * Keys blade of a Table API account.
*/ */
TableKeys = "tableKeys", TableKeys = "tableKeys",
/** /**

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

@@ -2,7 +2,7 @@
* JupyterLab applications based on jupyterLab components * JupyterLab applications based on jupyterLab components
*/ */
import { ServerConnection, TerminalManager } from "@jupyterlab/services"; import { ServerConnection, TerminalManager } from "@jupyterlab/services";
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal"; import { IMessage } from "@jupyterlab/services/lib/terminal/terminal";
import { Terminal } from "@jupyterlab/terminal"; import { Terminal } from "@jupyterlab/terminal";
import { Panel, Widget } from "@phosphor/widgets"; import { Panel, Widget } from "@phosphor/widgets";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
@@ -24,10 +24,6 @@ export class JupyterLabAppFactory {
this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh"); this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh");
} }
private isPostgresShellStarted(content: string | undefined) {
this.isShellStarted = content?.includes("citus=>");
}
constructor(closeTab: () => void) { constructor(closeTab: () => void) {
this.onShellExited = closeTab; this.onShellExited = closeTab;
this.isShellStarted = false; this.isShellStarted = false;
@@ -40,13 +36,10 @@ export class JupyterLabAppFactory {
case "Cassandra": case "Cassandra":
this.checkShellStarted = this.isCassandraShellStarted; this.checkShellStarted = this.isCassandraShellStarted;
break; break;
case "Postgres":
this.checkShellStarted = this.isPostgresShellStarted;
break;
} }
} }
public async createTerminalApp(serverSettings: ServerConnection.ISettings): Promise<ITerminalConnection | undefined> { public async createTerminalApp(serverSettings: ServerConnection.ISettings) {
const manager = new TerminalManager({ const manager = new TerminalManager({
serverSettings: serverSettings, serverSettings: serverSettings,
}); });
@@ -68,7 +61,7 @@ export class JupyterLabAppFactory {
if (!term) { if (!term) {
console.error("Failed starting terminal"); console.error("Failed starting terminal");
return undefined; return;
} }
term.title.closable = false; term.title.closable = false;
@@ -81,9 +74,6 @@ export class JupyterLabAppFactory {
// Attach the widget to the dom. // Attach the widget to the dom.
Widget.attach(panel, document.body); Widget.attach(panel, document.body);
// Switch focus to the terminal
term.activate();
// Handle resize events. // Handle resize events.
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
panel.update(); panel.update();
@@ -93,7 +83,5 @@ export class JupyterLabAppFactory {
window.addEventListener("unload", () => { window.addEventListener("unload", () => {
panel.dispose(); panel.dispose();
}); });
return session;
} }
} }

View File

@@ -1,5 +1,4 @@
import { ServerConnection } from "@jupyterlab/services"; import { ServerConnection } from "@jupyterlab/services";
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal";
import "@jupyterlab/terminal/style/index.css"; import "@jupyterlab/terminal/style/index.css";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import postRobot from "post-robot"; import postRobot from "post-robot";
@@ -42,7 +41,7 @@ const createServerSettings = (props: TerminalProps): ServerConnection.ISettings
return ServerConnection.makeSettings(options); return ServerConnection.makeSettings(options);
}; };
const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection | undefined> => { const initTerminal = async (props: TerminalProps) => {
// Initialize userContext (only properties which are needed by TelemetryProcessor) // Initialize userContext (only properties which are needed by TelemetryProcessor)
updateUserContext({ updateUserContext({
subscriptionId: props.subscriptionId, subscriptionId: props.subscriptionId,
@@ -56,12 +55,10 @@ const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection |
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data); const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
try { try {
const session = await new JupyterLabAppFactory(() => closeTab(props.tabId)).createTerminalApp(serverSettings); await new JupyterLabAppFactory(() => closeTab(props.tabId)).createTerminalApp(serverSettings);
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime); TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
return session;
} catch (error) { } catch (error) {
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime); TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
return undefined;
} }
}; };
@@ -73,7 +70,6 @@ const closeTab = (tabId: string): void => {
}; };
const main = async (): Promise<void> => { const main = async (): Promise<void> => {
let session: ITerminalConnection | undefined;
postRobot.on( postRobot.on(
"props", "props",
{ {
@@ -84,22 +80,7 @@ const main = async (): Promise<void> => {
// Typescript definition for event is wrong. So read props by casting to <any> // Typescript definition for event is wrong. So read props by casting to <any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (event as any).data as TerminalProps; const props = (event as any).data as TerminalProps;
session = await initTerminal(props); await initTerminal(props);
}
);
postRobot.on(
"sendMessage",
{
window: window.parent,
domain: window.location.origin,
},
async (event) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const message = (event as any).data as IMessage;
if (session) {
session.send(message);
}
} }
); );
}; };

View File

@@ -26,20 +26,6 @@ export interface CollectionCreationDefaults {
throughput: ThroughputDefaults; throughput: ThroughputDefaults;
} }
export interface Node {
text: string;
value: string;
ariaLabel: string;
}
export interface PostgresConnectionStrParams {
adminLogin: string;
enablePublicIpAccess: boolean;
nodes: Node[];
isMarlinServerGroup: boolean;
isFreeTier: boolean;
}
interface UserContext { interface UserContext {
readonly authType?: AuthType; readonly authType?: AuthType;
readonly masterKey?: string; readonly masterKey?: string;
@@ -66,8 +52,6 @@ interface UserContext {
collectionId: string; collectionId: string;
partitionKey?: string; partitionKey?: string;
}; };
readonly postgresConnectionStrParams?: PostgresConnectionStrParams;
readonly isReplica?: boolean;
collectionCreationDefaults: CollectionCreationDefaults; collectionCreationDefaults: CollectionCreationDefaults;
} }
@@ -102,7 +86,7 @@ function isAccountNewerThanThresholdInMs(createdAt: string, threshold: number) {
function updateUserContext(newContext: Partial<UserContext>): void { function updateUserContext(newContext: Partial<UserContext>): void {
if (newContext.databaseAccount) { if (newContext.databaseAccount) {
newContext.apiType = apiType(newContext.databaseAccount); newContext.apiType = "Postgres";
const isNewAccount = isAccountNewerThanThresholdInMs( const isNewAccount = isAccountNewerThanThresholdInMs(
newContext.databaseAccount?.systemData?.createdAt || "", newContext.databaseAccount?.systemData?.createdAt || "",
@@ -110,51 +94,43 @@ function updateUserContext(newContext: Partial<UserContext>): void {
); );
if (!localStorage.getItem(newContext.databaseAccount.id)) { if (!localStorage.getItem(newContext.databaseAccount.id)) {
if (newContext.isTryCosmosDBSubscription || isNewAccount) { if (newContext.apiType === "Postgres") {
if (newContext.apiType === "Postgres" && !newContext.isReplica) {
usePostgres.getState().setShowResetPasswordBubble(true);
usePostgres.getState().setShowPostgreTeachingBubble(true); usePostgres.getState().setShowPostgreTeachingBubble(true);
} else { localStorage.setItem(newContext.databaseAccount.id, "true");
} else if (userContext.isTryCosmosDBSubscription || isNewAccount) {
useCarousel.getState().setShouldOpen(true); useCarousel.getState().setShouldOpen(true);
localStorage.setItem(newContext.databaseAccount.id, "true"); localStorage.setItem(newContext.databaseAccount.id, "true");
traceOpen(Action.OpenCarousel); traceOpen(Action.OpenCarousel);
} }
} else if (newContext.apiType === "Postgres") {
usePostgres.getState().setShowPostgreTeachingBubble(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
}
} }
} }
Object.assign(userContext, newContext); Object.assign(userContext, newContext);
} }
function apiType(account: DatabaseAccount | undefined): ApiType { // function apiType(account: DatabaseAccount | undefined): ApiType {
if (!account) { // if (!account) {
return "SQL"; // return "SQL";
} // }
const capabilities = account.properties?.capabilities; // const capabilities = account.properties?.capabilities;
if (capabilities) { // if (capabilities) {
if (capabilities.find((c) => c.name === "EnableCassandra")) { // if (capabilities.find((c) => c.name === "EnableCassandra")) {
return "Cassandra"; // return "Cassandra";
} // }
if (capabilities.find((c) => c.name === "EnableGremlin")) { // if (capabilities.find((c) => c.name === "EnableGremlin")) {
return "Gremlin"; // return "Gremlin";
} // }
if (capabilities.find((c) => c.name === "EnableMongo")) { // if (capabilities.find((c) => c.name === "EnableMongo")) {
return "Mongo"; // return "Mongo";
} // }
if (capabilities.find((c) => c.name === "EnableTable")) { // if (capabilities.find((c) => c.name === "EnableTable")) {
return "Tables"; // return "Tables";
} // }
} // }
if (account.kind === "MongoDB" || account.kind === "Parse") { // if (account.kind === "MongoDB" || account.kind === "Parse") {
return "Mongo"; // return "Mongo";
} // }
if (account.kind === "Postgres") { // return "SQL";
return "Postgres"; // }
}
return "SQL";
}
export { userContext, updateUserContext }; export { userContext, updateUserContext };

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