mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 02:41:39 +00:00
Compare commits
3 Commits
defect2280
...
postgres_q
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af5fd699f9 | ||
|
|
d78610cd33 | ||
|
|
bc99ca372b |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -92,11 +92,11 @@ jobs:
|
||||
name: dist
|
||||
path: dist/
|
||||
- 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:
|
||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||
- 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:
|
||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||
endtoendemulator:
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
with:
|
||||
name: dist
|
||||
- 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 push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||
- uses: actions/upload-artifact@v2
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
name: dist
|
||||
- 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: 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 push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||
- uses: actions/upload-artifact@v2
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!doctype html>
|
||||
<html class="default no-js">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
@@ -10,292 +9,256 @@
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
<script async src="../assets/js/search.js" id="search-script"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="tsd-page-toolbar">
|
||||
<div class="container">
|
||||
<div class="table-wrap">
|
||||
<div class="table-cell" id="tsd-search" data-index="../assets/js/search.json" data-base="..">
|
||||
<div class="field">
|
||||
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
|
||||
<input id="tsd-search-field" type="text" />
|
||||
</div>
|
||||
<ul class="results">
|
||||
<li class="state loading">Preparing search index...</li>
|
||||
<li class="state failure">The search index is not available</li>
|
||||
</ul>
|
||||
<a href="../index.html" class="title">cosmos-explorer</a>
|
||||
<header>
|
||||
<div class="tsd-page-toolbar">
|
||||
<div class="container">
|
||||
<div class="table-wrap">
|
||||
<div class="table-cell" id="tsd-search" data-index="../assets/js/search.json" data-base="..">
|
||||
<div class="field">
|
||||
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
|
||||
<input id="tsd-search-field" type="text" />
|
||||
</div>
|
||||
<div class="table-cell" id="tsd-widgets">
|
||||
<div id="tsd-filter">
|
||||
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
|
||||
<div class="tsd-filter-group">
|
||||
<div class="tsd-select" id="tsd-filter-visibility">
|
||||
<span class="tsd-select-label">All</span>
|
||||
<ul class="tsd-select-list">
|
||||
<li data-value="public">Public</li>
|
||||
<li data-value="protected">Public/Protected</li>
|
||||
<li data-value="private" class="selected">All</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="checkbox" id="tsd-filter-inherited" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
|
||||
<input type="checkbox" id="tsd-filter-externals" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
|
||||
<ul class="results">
|
||||
<li class="state loading">Preparing search index...</li>
|
||||
<li class="state failure">The search index is not available</li>
|
||||
</ul>
|
||||
<a href="../index.html" class="title">cosmos-explorer</a>
|
||||
</div>
|
||||
<div class="table-cell" id="tsd-widgets">
|
||||
<div id="tsd-filter">
|
||||
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
|
||||
<div class="tsd-filter-group">
|
||||
<div class="tsd-select" id="tsd-filter-visibility">
|
||||
<span class="tsd-select-label">All</span>
|
||||
<ul class="tsd-select-list">
|
||||
<li data-value="public">Public</li>
|
||||
<li data-value="protected">Public/Protected</li>
|
||||
<li data-value="private" class="selected">All</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="checkbox" id="tsd-filter-inherited" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
|
||||
<input type="checkbox" id="tsd-filter-externals" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
|
||||
</div>
|
||||
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
|
||||
</div>
|
||||
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tsd-page-title">
|
||||
<div class="container">
|
||||
<ul class="tsd-breadcrumb">
|
||||
<li>
|
||||
<a href="../modules.html">cosmos-explorer</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../modules/selfserve_selfserveutils.html">SelfServe/SelfServeUtils</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="selfserve_selfserveutils.bladetype.html">BladeType</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>Enumeration BladeType</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tsd-page-title">
|
||||
<div class="container">
|
||||
<ul class="tsd-breadcrumb">
|
||||
<li>
|
||||
<a href="../modules.html">cosmos-explorer</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../modules/selfserve_selfserveutils.html">SelfServe/SelfServeUtils</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="selfserve_selfserveutils.bladetype.html">BladeType</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>Enumeration BladeType</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container container-main">
|
||||
<div class="row">
|
||||
<div class="col-8 col-content">
|
||||
<section class="tsd-panel tsd-comment">
|
||||
</div>
|
||||
</header>
|
||||
<div class="container container-main">
|
||||
<div class="row">
|
||||
<div class="col-8 col-content">
|
||||
<section class="tsd-panel tsd-comment">
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Portal Blade types</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-index-group">
|
||||
<h2>Index</h2>
|
||||
<section class="tsd-panel tsd-index-panel">
|
||||
<div class="tsd-index-content">
|
||||
<section class="tsd-index-section ">
|
||||
<h3>Enumeration members</h3>
|
||||
<ul class="tsd-index-list">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-member-group ">
|
||||
<h2>Enumeration members</h2>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="cassandrakeys" class="tsd-anchor"></a>
|
||||
<h3>Cassandra<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Cassandra<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = "cassandraDbKeys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Portal Blade types</p>
|
||||
<p>Keys blade of a Cassandra API account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-index-group">
|
||||
<h2>Index</h2>
|
||||
<section class="tsd-panel tsd-index-panel">
|
||||
<div class="tsd-index-content">
|
||||
<section class="tsd-index-section ">
|
||||
<h3>Enumeration members</h3>
|
||||
<ul class="tsd-index-list">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="gremlinkeys" class="tsd-anchor"></a>
|
||||
<h3>Gremlin<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Gremlin<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = "keys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Gremlin API account.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-member-group ">
|
||||
<h2>Enumeration members</h2>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="cassandrakeys" class="tsd-anchor"></a>
|
||||
<h3>Cassandra<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Cassandra<wbr>Keys<span
|
||||
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
|
||||
"cassandraDbKeys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Azure Cosmos DB for Apache Cassandra account.</p>
|
||||
</div>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="metrics" class="tsd-anchor"></a>
|
||||
<h3>Metrics</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Metrics<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = "metrics"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Metrics blade of an Azure Cosmos DB account.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="gremlinkeys" class="tsd-anchor"></a>
|
||||
<h3>Gremlin<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Gremlin<wbr>Keys<span
|
||||
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
|
||||
"keys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Azure Cosmos DB for Apache Gremlin account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="metrics" class="tsd-anchor"></a>
|
||||
<h3>Metrics</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Metrics<span class="tsd-signature-symbol">:</span>
|
||||
<span class="tsd-signature-symbol"> = "metrics"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Metrics blade of an Azure Cosmos DB account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="mongokeys" class="tsd-anchor"></a>
|
||||
<h3>Mongo<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Mongo<wbr>Keys<span
|
||||
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
|
||||
"mongoDbKeys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Azure Cosmos DB for MongoDB account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="sqlkeys" class="tsd-anchor"></a>
|
||||
<h3>Sql<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Sql<wbr>Keys<span class="tsd-signature-symbol">:</span>
|
||||
<span class="tsd-signature-symbol"> = "keys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Azure Cosmos DB for NoSQL account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="tablekeys" class="tsd-anchor"></a>
|
||||
<h3>Table<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Table<wbr>Keys<span
|
||||
class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> =
|
||||
"tableKeys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Azure Cosmos DB for Table account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
|
||||
<nav class="tsd-navigation primary">
|
||||
<ul>
|
||||
<li class=" ">
|
||||
<a href="../modules.html">Modules</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve.html">Self<wbr>Serve</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve___what_is_currently_supported_.html">Self<wbr>Serve -<wbr>
|
||||
<wbr>What is currently supported?</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve_decorators.html">Self<wbr>Serve/<wbr>Decorators</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a
|
||||
href="../modules/selfserve_selfservetelemetryprocessor.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Telemetry<wbr>Processor</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a
|
||||
href="../modules/selfserve_selfservetypes.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Types</a>
|
||||
</li>
|
||||
<li class="current tsd-kind-module">
|
||||
<a
|
||||
href="../modules/selfserve_selfserveutils.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Utils</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="tsd-navigation secondary menu-sticky">
|
||||
<ul class="before-current">
|
||||
</ul>
|
||||
<ul class="current">
|
||||
<li class="current tsd-kind-enum tsd-parent-kind-module">
|
||||
<a href="selfserve_selfserveutils.bladetype.html" class="tsd-kind-icon">Blade<wbr>Type</a>
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="after-current">
|
||||
<li class=" tsd-kind-enum tsd-parent-kind-module">
|
||||
<a href="selfserve_selfserveutils.selfservetype.html"
|
||||
class="tsd-kind-icon">Self<wbr>Serve<wbr>Type</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-function tsd-parent-kind-module">
|
||||
<a href="../modules/selfserve_selfserveutils.html#generatebladelink"
|
||||
class="tsd-kind-icon">generate<wbr>Blade<wbr>Link</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="mongokeys" class="tsd-anchor"></a>
|
||||
<h3>Mongo<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Mongo<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = "mongoDbKeys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Mongo API account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="sqlkeys" class="tsd-anchor"></a>
|
||||
<h3>Sql<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Sql<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = "keys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a SQL API account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
|
||||
<a name="tablekeys" class="tsd-anchor"></a>
|
||||
<h3>Table<wbr>Keys</h3>
|
||||
<div class="tsd-signature tsd-kind-icon">Table<wbr>Keys<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = "tableKeys"</span></div>
|
||||
<aside class="tsd-sources">
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Keys blade of a Table API account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
|
||||
<nav class="tsd-navigation primary">
|
||||
<ul>
|
||||
<li class=" ">
|
||||
<a href="../modules.html">Modules</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve.html">Self<wbr>Serve</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve___what_is_currently_supported_.html">Self<wbr>Serve -<wbr> <wbr>What is currently supported?</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve_decorators.html">Self<wbr>Serve/<wbr>Decorators</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve_selfservetelemetryprocessor.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Telemetry<wbr>Processor</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-module">
|
||||
<a href="../modules/selfserve_selfservetypes.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Types</a>
|
||||
</li>
|
||||
<li class="current tsd-kind-module">
|
||||
<a href="../modules/selfserve_selfserveutils.html">Self<wbr>Serve/<wbr>Self<wbr>Serve<wbr>Utils</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="tsd-navigation secondary menu-sticky">
|
||||
<ul class="before-current">
|
||||
</ul>
|
||||
<ul class="current">
|
||||
<li class="current tsd-kind-enum tsd-parent-kind-module">
|
||||
<a href="selfserve_selfserveutils.bladetype.html" class="tsd-kind-icon">Blade<wbr>Type</a>
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="after-current">
|
||||
<li class=" tsd-kind-enum tsd-parent-kind-module">
|
||||
<a href="selfserve_selfserveutils.selfservetype.html" class="tsd-kind-icon">Self<wbr>Serve<wbr>Type</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-function tsd-parent-kind-module">
|
||||
<a href="../modules/selfserve_selfserveutils.html#generatebladelink" class="tsd-kind-icon">generate<wbr>Blade<wbr>Link</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="with-border-bottom">
|
||||
<div class="container">
|
||||
<h2>Legend</h2>
|
||||
<div class="tsd-legend-group">
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li>
|
||||
<li class="tsd-kind-type-alias"><span class="tsd-kind-icon">Type alias</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="with-border-bottom">
|
||||
<div class="container">
|
||||
<h2>Legend</h2>
|
||||
<div class="tsd-legend-group">
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li>
|
||||
<li class="tsd-kind-type-alias"><span class="tsd-kind-icon">Type alias</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="container tsd-generator">
|
||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
||||
</div>
|
||||
<div class="overlay"></div>
|
||||
<script src="../assets/js/main.js"></script>
|
||||
</footer>
|
||||
<div class="container tsd-generator">
|
||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
||||
</div>
|
||||
<div class="overlay"></div>
|
||||
<script src="../assets/js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!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">
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 649 B After Width: | Height: | Size: 652 B |
@@ -18,8 +18,7 @@ module.exports = {
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
|
||||
collectCoverage: process.env.skipCodeCoverage === "true" ? false : true,
|
||||
collectCoverage: true,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
|
||||
@@ -3,337 +3,337 @@
|
||||
@import "../Common/Constants";
|
||||
|
||||
.query-panel {
|
||||
display: table;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border-top: 1px solid #dddddd;
|
||||
/*[{environment-commandbar-toolbar-separator}]*/
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
padding: 2px 0px 0px 2px;
|
||||
resize: vertical;
|
||||
display: table;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border-top: 1px solid #DDDDDD;
|
||||
/*[{environment-commandbar-toolbar-separator}]*/
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
padding: 2px 0px 0px 2px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.query-panel .row {
|
||||
display: table-row;
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.query-panel .row .cell {
|
||||
display: table-cell;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.query-panel.transition-in {
|
||||
display: table;
|
||||
top: 0px;
|
||||
-webkit-transition: top 2s linear;
|
||||
-ms-transition: top 2s linear;
|
||||
-moz-transition: top 2s linear;
|
||||
-khtml-transition: top 2s linear;
|
||||
-o-transition: top 2s linear;
|
||||
transition: top 2s linear;
|
||||
display: table;
|
||||
top: 0px;
|
||||
-webkit-transition: top 2s linear;
|
||||
-ms-transition: top 2s linear;
|
||||
-moz-transition: top 2s linear;
|
||||
-khtml-transition: top 2s linear;
|
||||
-o-transition: top 2s linear;
|
||||
transition: top 2s linear;
|
||||
}
|
||||
|
||||
.query-builder {
|
||||
width: 100%;
|
||||
padding-right: @DefaultSpace;
|
||||
border-bottom: 1px solid @BaseMedium;
|
||||
margin-bottom: @DefaultSpace;
|
||||
width:100%;
|
||||
padding-right: @DefaultSpace;
|
||||
border-bottom: 1px solid @BaseMedium;
|
||||
margin-bottom: @DefaultSpace;
|
||||
}
|
||||
|
||||
.query-builder-toolbar {
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
min-width: 600px;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
min-width: 600px;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid #DDDDDD;
|
||||
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
margin: 2px 0px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
margin: 2px 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button {
|
||||
min-width: 0px;
|
||||
padding: 0px;
|
||||
margin-left: 2px;
|
||||
background-color: transparent;
|
||||
border: solid transparent;
|
||||
min-width: 0px;
|
||||
padding: 0px;
|
||||
margin-left: 2px;
|
||||
background-color: transparent;
|
||||
border: solid transparent;
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
|
||||
outline: 2px solid dodgerblue;
|
||||
/*[2px solid {common-common-controls-button-border-hover}]*/
|
||||
outline: 2px solid dodgerblue;
|
||||
/*[2px solid {common-common-controls-button-border-hover}]*/
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
|
||||
background-color: #cccedb;
|
||||
/*[{common-controls-button-hover-background}]*/
|
||||
background-color: #CCCEDB;
|
||||
/*[{common-controls-button-hover-background}]*/
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
|
||||
background-color: #e6e7ed;
|
||||
/*[{common-controls-inner-tab-active-background}]*/
|
||||
outline: none;
|
||||
background-color: #E6E7ED;
|
||||
/*[{common-controls-inner-tab-active-background}]*/
|
||||
outline: none
|
||||
}
|
||||
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
|
||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
|
||||
background-color: #ffffff;
|
||||
/*[{common-controls-button-disabled-background}]*/
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
background-color: #ffffff;
|
||||
/*[{common-controls-button-disabled-background}]*/
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.tableContainer {
|
||||
overflow: hidden;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
overflow: hidden;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
}
|
||||
|
||||
.tablesQueryTab {
|
||||
padding-left: @MediumSpace;
|
||||
width: 100%;
|
||||
margin-bottom: @LargeSpace;
|
||||
.tablesQueryTab{
|
||||
padding-left: @MediumSpace;
|
||||
width: 100%;
|
||||
margin-bottom:@LargeSpace;
|
||||
}
|
||||
|
||||
.entity-error-Img {
|
||||
width: @WarningErrorIconSize;
|
||||
height: @WarningErrorIconSize;
|
||||
margin: @DefaultSpace 0px 0px @SmallSpace;
|
||||
width: @WarningErrorIconSize;
|
||||
height: @WarningErrorIconSize;
|
||||
margin: @DefaultSpace 0px 0px @SmallSpace;
|
||||
}
|
||||
|
||||
.query-editor-panel {
|
||||
margin-right: 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 25px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
margin-right: 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 25px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.query-editor-text {
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: solid 1px #a9acb3;
|
||||
/*[{plugin-textbox-disabled-color}]*/
|
||||
resize: none;
|
||||
margin-top: -39px;
|
||||
background-color: #ddd;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: solid 1px #A9ACB3;
|
||||
/*[{plugin-textbox-disabled-color}]*/
|
||||
resize: none;
|
||||
margin-top: -39px;
|
||||
background-color: #ddd;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.error-bar {
|
||||
padding: @LargeSpace 34px @MediumSpace 24px;
|
||||
padding: @LargeSpace 34px @MediumSpace 24px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: @BaseLow;
|
||||
padding: @DefaultSpace;
|
||||
display: inline-flex;
|
||||
background-color: @BaseLow;
|
||||
padding: @DefaultSpace;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
padding-left: @MediumSpace;
|
||||
padding-left: @MediumSpace;
|
||||
}
|
||||
|
||||
.query-editor-text-invalid {
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: 1px solid #e51400;
|
||||
resize: none;
|
||||
margin-top: -30px;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
border: 1px solid #e51400;
|
||||
resize: none;
|
||||
margin-top: -30px;
|
||||
}
|
||||
|
||||
.query-editor-panel .warning-bar {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background-color: #ffffff;
|
||||
/*[{plugin-background-color}]*/
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
}
|
||||
|
||||
.query-editor-panel .warning-bar .warning-message {
|
||||
display: inline-flex;
|
||||
padding-top: 2px;
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
padding-top: 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.query-editor-panel .warning-bar .warning-message .warning-text {
|
||||
margin-left: 2px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.advanced-options-panel {
|
||||
margin-bottom: @DefaultSpace;
|
||||
.advanced-options-panel{
|
||||
margin-bottom: @DefaultSpace;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-heading .advanced-title {
|
||||
display: inline-flex;
|
||||
margin-left: 27px;
|
||||
margin-top: 10px;
|
||||
cursor: default;
|
||||
display: inline-flex;
|
||||
margin-left: 27px;
|
||||
margin-top: 10px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options {
|
||||
margin-left: 32px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid transparent;
|
||||
margin-left: 32px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 12px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 12px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .top .top-input {
|
||||
width: 100px;
|
||||
word-spacing: normal;
|
||||
color: #1e1e1e;
|
||||
/*[{common-controls-button-foreground}]*/
|
||||
border: 1px solid #cccedb;
|
||||
/*[1px solid {plugin-textbox-border-color}]*/
|
||||
height: 20px;
|
||||
margin-left: 8px;
|
||||
width: 100px;
|
||||
word-spacing: normal;
|
||||
color: #1E1E1E;
|
||||
/*[{common-controls-button-foreground}]*/
|
||||
border: 1px solid #CCCEDB;
|
||||
/*[1px solid {plugin-textbox-border-color}]*/
|
||||
height: 20px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .top .invalid-top {
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .select {
|
||||
margin-top: 18px;
|
||||
display: inline-flex;
|
||||
margin-top: 18px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.advanced-options-icon {
|
||||
margin-left: 2px;
|
||||
vertical-align: sub;
|
||||
margin-left: 2px;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .select .select-options-text {
|
||||
margin-left: 4px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.advanced-options-panel .advanced-options .select .select-options-link {
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.query-panel .row .column-headers .Field {
|
||||
padding-left: 95px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 95px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.clause-table {
|
||||
border-spacing: 0px;
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: -3px;
|
||||
border-spacing: 0px;
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.clause-table-row {
|
||||
display: row;
|
||||
margin-bottom: 10px;
|
||||
display: row;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clause-table-cell {
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.action-column > button,
|
||||
.group-control-header > button,
|
||||
.group-indicator-column > button {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
.action-column>button,
|
||||
.group-control-header>button,
|
||||
.group-indicator-column>button {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.group-control-header > button:disabled {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
.group-control-header>button:disabled {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.clause-table-field {
|
||||
width: 100%;
|
||||
border: 1px solid #bbbbbb;
|
||||
width: 100%;
|
||||
border: 1px solid #bbbbbb;
|
||||
}
|
||||
|
||||
.clause-table-cell button {
|
||||
height: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.clause-table-cell input[type="checkbox"] {
|
||||
padding: 0px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.and-or-svg {
|
||||
margin-top: -8px;
|
||||
margin-right: -5px;
|
||||
margin-top: -8px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
.scroll-box {
|
||||
border-bottom: 1px transparent #ddd;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
border-top: 1px transparent #ddd;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
max-height: 20vh;
|
||||
width: 100%;
|
||||
border-bottom: 1px transparent #DDD;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
border-top: 1px transparent #DDD;
|
||||
/*[1px solid {plugin-table-border-color}]*/
|
||||
max-height: 20vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.and-or-column,
|
||||
.and-or-header {
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
padding-left: 5px;
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.operator-column,
|
||||
.operator-header {
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
min-width: 65px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.field-header,
|
||||
.field-column {
|
||||
min-width: 125px;
|
||||
padding-right: 10px;
|
||||
min-width: 125px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.type-header,
|
||||
.type-column {
|
||||
min-width: 85px;
|
||||
min-width: 85px;
|
||||
}
|
||||
|
||||
.and-or-column,
|
||||
@@ -345,41 +345,41 @@ input::-webkit-inner-spin-button {
|
||||
.type-header,
|
||||
.type-column,
|
||||
.action-header {
|
||||
padding-right: 10px;
|
||||
margin-bottom: 8px;
|
||||
padding-right: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.value-header,
|
||||
.value-column,
|
||||
.time-column {
|
||||
min-width: 230px;
|
||||
padding: 0px 4px 0px 0px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
min-width: 230px;
|
||||
padding: 0px 4px 0px 0px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.group-control-header,
|
||||
.group-control-column {
|
||||
min-width: 25px;
|
||||
text-align: right;
|
||||
min-width: 25px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.group-indicator-table {
|
||||
border-spacing: 0px;
|
||||
min-height: 24px;
|
||||
border-spacing: 0px;
|
||||
min-height: 24px
|
||||
}
|
||||
|
||||
.group-indicator-column {
|
||||
min-width: 21px;
|
||||
padding: 0px;
|
||||
border-style: none;
|
||||
height: 29px;
|
||||
min-width: 21px;
|
||||
padding: 0px;
|
||||
border-style: none;
|
||||
height: 29px;
|
||||
}
|
||||
|
||||
.clause-table-cell.action-column,
|
||||
.clause-table-cell.action-column,
|
||||
.clause-table-cell.action-header {
|
||||
min-width: 60px;
|
||||
padding-left: @SmallSpace;
|
||||
min-width: 60px;
|
||||
padding-left: @SmallSpace;
|
||||
}
|
||||
|
||||
.action-header,
|
||||
@@ -388,14 +388,15 @@ input::-webkit-inner-spin-button {
|
||||
.operator-header,
|
||||
.value-header,
|
||||
.and-or-header {
|
||||
padding-right: 4px;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 4px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.header-background {
|
||||
background-color: #ffffff;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
/*.type-header {
|
||||
padding-right: 4px;
|
||||
}
|
||||
@@ -409,111 +410,112 @@ input::-webkit-inner-spin-button {
|
||||
}*/
|
||||
|
||||
.clause-table-field[readonly] {
|
||||
background-color: #eeeef2;
|
||||
/*[{plugin-table-header-background-color}]*/
|
||||
border: 1px solid #cccedb;
|
||||
/*[{plugin-table-border-color}]*/
|
||||
background-color: #EEEEF2;
|
||||
/*[{plugin-table-header-background-color}]*/
|
||||
border: 1px solid #CCCEDB;
|
||||
/*[{plugin-table-border-color}]*/
|
||||
}
|
||||
|
||||
.addClause-title {
|
||||
/*[{common-common-controls-button-border-hover}]*/
|
||||
cursor: pointer;
|
||||
margin-left: -5px;
|
||||
/*[{common-common-controls-button-border-hover}]*/
|
||||
cursor: pointer;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.addClause {
|
||||
width: 125px;
|
||||
padding: 8px 0px 5px 5px;
|
||||
border: 1px solid #fff;
|
||||
margin-left: 5px;
|
||||
width: 125px;
|
||||
padding: 8px 0px 5px 5px;
|
||||
border: 1px solid #fff;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.addClause:hover {
|
||||
.hover();
|
||||
.hover();
|
||||
}
|
||||
|
||||
.addClause:active {
|
||||
.active();
|
||||
border: 1px dashed @AccentMedium;
|
||||
.active();
|
||||
border: 1px dashed @AccentMedium;
|
||||
}
|
||||
|
||||
.clause-table-row {
|
||||
min-width: 550px;
|
||||
width: 100%;
|
||||
min-width: 550px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clause-table-field field-column {
|
||||
min-width: 75px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
min-width: 75px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clause-table-field field-input {
|
||||
min-width: 54px;
|
||||
margin-left: -78px;
|
||||
height: 25px;
|
||||
border: none;
|
||||
min-width: 54px;
|
||||
margin-left: -78px;
|
||||
height: 25px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.query-panel .row .spacing {
|
||||
padding-bottom: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.query-panel .divider.horizontal {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.inline-div {
|
||||
display: inline;
|
||||
display: inline
|
||||
}
|
||||
|
||||
.querybuilder-addpropertyImg,
|
||||
.querybuilder-cancelImg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: 3px;
|
||||
margin-bottom: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: 3px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.addclauseProperty-Img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 12px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.entity-Add-Cancel {
|
||||
// padding: @DefaultSpace @SmallSpace @SmallSpace;
|
||||
cursor: pointer;
|
||||
padding: @DefaultSpace @SmallSpace @SmallSpace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.entity-Add-Cancel:hover {
|
||||
.hover();
|
||||
.hover();
|
||||
}
|
||||
|
||||
.entity-Add-Cancel:active {
|
||||
.active();
|
||||
.active();
|
||||
}
|
||||
|
||||
.query-builder-isDisabled {
|
||||
border: 1px solid #cccedb;
|
||||
color: #ccc;
|
||||
border: 1px solid #CCCEDB;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.edit-value-text {
|
||||
padding-left: @DefaultSpace;
|
||||
padding-left: @DefaultSpace;
|
||||
}
|
||||
|
||||
.expand-triangle {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.expand-triangle-right {
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.clause-table {
|
||||
@@ -522,4 +524,4 @@ input::-webkit-inner-spin-button {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}*/
|
||||
}*/
|
||||
@@ -2576,10 +2576,6 @@ a:link {
|
||||
.querydropdown.placeholderVisible {
|
||||
font-style: italic;
|
||||
}
|
||||
.querydropdown.placeholderVisible::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: #767474;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.querydropdown:hover {
|
||||
background-color: @AccentLow;
|
||||
@@ -3091,4 +3087,3 @@ a:link {
|
||||
background: white;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,12 @@
|
||||
overflow: auto;
|
||||
|
||||
.databaseHeader {
|
||||
padding: 1px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.collectionHeader {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loadMoreHeader {
|
||||
color: RGB(5, 99, 193);
|
||||
}
|
||||
}
|
||||
|
||||
.notebookResourceTree {
|
||||
@@ -28,4 +23,6 @@
|
||||
.clickDisabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
import { NormalizedEventKey } from "./Constants";
|
||||
|
||||
export interface CollapsedResourceTreeProps {
|
||||
@@ -45,7 +45,7 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||
</span>
|
||||
<span className="collectionCollapsed">
|
||||
<span>{getApiShortDisplayName()}</span>
|
||||
<span>{userContext.apiType} API</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -45,8 +45,6 @@ export class ArmResourceTypes {
|
||||
|
||||
export class BackendDefaults {
|
||||
public static partitionKeyKind = "Hash";
|
||||
public static partitionKeyMultiHash = "MultiHash";
|
||||
public static maxNumMultiHashPartition = 2;
|
||||
public static singlePartitionStorageInGb: string = "10";
|
||||
public static multiPartitionStorageInGb: string = "100";
|
||||
public static maxChangeFeedRetentionDuration: number = 10;
|
||||
@@ -141,7 +139,7 @@ export class Queries {
|
||||
public static UnlimitedPageOption: string = "unlimited";
|
||||
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 containersPerPage: number = 50;
|
||||
|
||||
public static QueryEditorMinHeightRatio: number = 0.1;
|
||||
public static QueryEditorMaxHeightRatio: number = 0.4;
|
||||
public static readonly DefaultMaxDegreeOfParallelism = 6;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { OfferResponse } from "@azure/cosmos";
|
||||
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
||||
import * as OfferUtility from "./OfferUtility";
|
||||
import { SDKOfferDefinition, Offer } from "../Contracts/DataModels";
|
||||
import { OfferResponse } from "@azure/cosmos";
|
||||
|
||||
describe("parseSDKOfferResponse", () => {
|
||||
it("manual throughput", () => {
|
||||
@@ -31,26 +31,6 @@ describe("parseSDKOfferResponse", () => {
|
||||
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", () => {
|
||||
const mockOfferDefinition = {
|
||||
content: {
|
||||
|
||||
@@ -6,7 +6,6 @@ import Explorer from "../Explorer/Explorer";
|
||||
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
||||
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
|
||||
import { NormalizedEventKey } from "./Constants";
|
||||
|
||||
export interface ResourceTreeContainerProps {
|
||||
@@ -43,7 +42,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
||||
{/* Collections Window Title/Command Bar - Start */}
|
||||
<div className="collectiontitle">
|
||||
<div className="coltitle">
|
||||
<span className="titlepadcol">{getApiShortDisplayName()}</span>
|
||||
<span className="titlepadcol">{userContext.apiType} API</span>
|
||||
<div className="float-right">
|
||||
<span
|
||||
className="padimgcolrefresh"
|
||||
|
||||
@@ -73,17 +73,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
|
||||
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 { Int, Smallint, Tinyint } = CassandraType;
|
||||
const { Double, Int32, Int64 } = TableType;
|
||||
@@ -137,30 +126,12 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
/>
|
||||
{!isEntityValueDisable && (
|
||||
<TooltipHost content="Edit property" id="editTooltip">
|
||||
<div tabIndex={0}>
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={EditIcon}
|
||||
alt="editEntity"
|
||||
id="editEntity"
|
||||
onClick={onEditEntity}
|
||||
tabIndex={0}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
</div>
|
||||
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
|
||||
</TooltipHost>
|
||||
)}
|
||||
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
|
||||
<TooltipHost content="Delete property" id="deleteTooltip">
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={DeleteIcon}
|
||||
alt="delete entity"
|
||||
id="deleteEntity"
|
||||
onClick={onDeleteEntity}
|
||||
tabIndex={0}
|
||||
onKeyPress={handleKeyPressdelete}
|
||||
/>
|
||||
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
|
||||
</TooltipHost>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
||||
return (
|
||||
<span>
|
||||
<TooltipHost content={children}>
|
||||
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
|
||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationCons
|
||||
import { client } from "../CosmosClient";
|
||||
import { getEntityName } from "../DocumentUtility";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { getPartitionKeyValue } from "./getPartitionKeyValue";
|
||||
|
||||
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
||||
const entityName: string = getEntityName();
|
||||
@@ -14,7 +13,7 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
||||
await client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
||||
.delete();
|
||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||
} catch (error) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Queries } from "Common/Constants";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
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[]> {
|
||||
let rpResponse;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { HttpHeaders } from "../Constants";
|
||||
import { client } from "../CosmosClient";
|
||||
import { getEntityName } from "../DocumentUtility";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { getPartitionKeyValue } from "./getPartitionKeyValue";
|
||||
|
||||
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
||||
const entityName = getEntityName();
|
||||
@@ -22,7 +21,8 @@ export const readDocument = async (collection: CollectionBase, documentId: Docum
|
||||
const response = await client()
|
||||
.database(collection.databaseId)
|
||||
.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);
|
||||
|
||||
return response?.resource;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationCons
|
||||
import { client } from "../CosmosClient";
|
||||
import { getEntityName } from "../DocumentUtility";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { getPartitionKeyValue } from "./getPartitionKeyValue";
|
||||
|
||||
export const updateDocument = async (
|
||||
collection: CollectionBase,
|
||||
@@ -26,7 +25,7 @@ export const updateDocument = async (
|
||||
const response = await client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
||||
.replace(newDocument, options);
|
||||
|
||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||
|
||||
@@ -33,8 +33,6 @@ export interface DatabaseAccountExtendedProperties {
|
||||
privateEndpointConnections?: unknown[];
|
||||
capacity?: { totalThroughputLimit: number };
|
||||
locations?: DatabaseAccountResponseLocation[];
|
||||
postgresqlEndpoint?: string;
|
||||
publicNetworkAccess?: string;
|
||||
}
|
||||
|
||||
export interface DatabaseAccountResponseLocation {
|
||||
@@ -156,11 +154,6 @@ export interface Collection extends Resource {
|
||||
requestSchema?: () => void;
|
||||
}
|
||||
|
||||
export interface CollectionsWithPagination {
|
||||
continuationToken?: string;
|
||||
collections?: Collection[];
|
||||
}
|
||||
|
||||
export interface Database extends Resource {
|
||||
collections?: Collection[];
|
||||
}
|
||||
@@ -573,16 +566,6 @@ export interface ContainerConnectionInfo {
|
||||
//need to add ram and rom info
|
||||
}
|
||||
|
||||
export interface PostgresFirewallRule {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
properties: {
|
||||
startIpAddress: string;
|
||||
endIpAddress: string;
|
||||
};
|
||||
}
|
||||
|
||||
export enum PhoenixErrorType {
|
||||
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
||||
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
||||
|
||||
@@ -35,9 +35,6 @@ export enum MessageTypes {
|
||||
RefreshDatabaseAccount,
|
||||
CloseTab,
|
||||
OpenQuickstartBlade,
|
||||
OpenPostgreSQLPasswordReset,
|
||||
OpenPostgresNetworkingBlade,
|
||||
OpenCosmosDBNetworkingBlade,
|
||||
}
|
||||
|
||||
export { Versions, ActionContracts, Diagnostics };
|
||||
|
||||
@@ -87,13 +87,13 @@ export interface Database extends TreeNode {
|
||||
isDatabaseExpanded: ko.Observable<boolean>;
|
||||
isDatabaseShared: ko.Computed<boolean>;
|
||||
isSampleDB?: boolean;
|
||||
collectionsContinuationToken?: string;
|
||||
|
||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||
|
||||
expandDatabase(): Promise<void>;
|
||||
collapseDatabase(): void;
|
||||
|
||||
loadCollections(restart?: boolean): Promise<void>;
|
||||
loadCollections(): Promise<void>;
|
||||
findCollectionWithId(collectionId: string): Collection;
|
||||
openAddCollection(database: Database, event: MouseEvent): void;
|
||||
onSettingsClick: () => void;
|
||||
@@ -186,6 +186,7 @@ export interface Collection extends CollectionBase {
|
||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
||||
|
||||
getLabel(): string;
|
||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||
}
|
||||
|
||||
@@ -371,7 +372,6 @@ export enum TerminalKind {
|
||||
Default = 0,
|
||||
Mongo = 1,
|
||||
Cassandra = 2,
|
||||
Postgres = 3,
|
||||
}
|
||||
|
||||
export interface DataExplorerInputsFrame {
|
||||
@@ -395,11 +395,6 @@ export interface DataExplorerInputsFrame {
|
||||
sharedThroughputDefault?: number;
|
||||
dataExplorerVersion?: string;
|
||||
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[];
|
||||
features?: {
|
||||
[key: string]: string;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Wrapper around Notebook server terminal
|
||||
*/
|
||||
|
||||
import { useTerminal } from "hooks/useTerminal";
|
||||
import postRobot from "post-robot";
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
@@ -41,7 +40,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
||||
|
||||
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||
useTerminal.getState().setTerminal(this.terminalWindow);
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
|
||||
@@ -76,8 +74,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
||||
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
|
||||
return this.props.databaseAccount?.properties.postgresqlEndpoint;
|
||||
}
|
||||
|
||||
if (terminalEndpoint) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HoverCard, HoverCardType, Icon, Label, Link, Stack } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "@fluentui/react";
|
||||
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
|
||||
import "./InfoComponent.less";
|
||||
|
||||
@@ -41,7 +41,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<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" } }} />
|
||||
<Label className="infoLabelMain">Help</Label>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,6 @@ exports[`InfoComponent renders 1`] = `
|
||||
>
|
||||
<div
|
||||
className="infoPanelMain"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Icon
|
||||
className="infoIconMain"
|
||||
|
||||
@@ -310,9 +310,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
/>
|
||||
)}
|
||||
|
||||
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && (
|
||||
<Text>Large {this.partitionKeyName.toLowerCase()} has been enabled</Text>
|
||||
)}
|
||||
{this.isLargePartitionKeyEnabled() && <Text>Large {this.partitionKeyName.toLowerCase()} has been enabled</Text>}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ interface ThroughputInputAutoPilotV3State {
|
||||
spendAckChecked: boolean;
|
||||
exceedFreeTierThroughput: boolean;
|
||||
}
|
||||
|
||||
export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
ThroughputInputAutoPilotV3Props,
|
||||
ThroughputInputAutoPilotV3State
|
||||
@@ -623,10 +624,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
return (
|
||||
<>
|
||||
{warningMessage && (
|
||||
<MessageBar
|
||||
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||
role="alert"
|
||||
>
|
||||
<MessageBar messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}>
|
||||
{warningMessage}
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
"iconName": "WarningSolid",
|
||||
}
|
||||
}
|
||||
role="alert"
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
|
||||
@@ -35,7 +35,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
@@ -112,7 +111,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import "./TabComponent.less";
|
||||
|
||||
export interface TabContent {
|
||||
@@ -18,15 +19,11 @@ interface TabComponentProps {
|
||||
onTabIndexChange: (newIndex: number) => void;
|
||||
hideHeader: boolean;
|
||||
}
|
||||
interface TabRefs {
|
||||
[key: string]: HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* We assume there's at least one tab
|
||||
*/
|
||||
export class TabComponent extends React.Component<TabComponentProps> {
|
||||
private tabRefs: TabRefs = {};
|
||||
public constructor(props: TabComponentProps) {
|
||||
super(props);
|
||||
|
||||
@@ -36,41 +33,11 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
state = {
|
||||
activeTabIndex: this.props.currentTabIndex,
|
||||
};
|
||||
|
||||
private setActiveTab(index: number): void {
|
||||
this.setState({ activeTabIndex: index });
|
||||
this.props.onTabIndexChange(index);
|
||||
}
|
||||
private setIndex = (index: number) => {
|
||||
const tab = this.tabRefs[index];
|
||||
if (tab) {
|
||||
tab.focus();
|
||||
this.setState({ activeTabIndex: index });
|
||||
}
|
||||
};
|
||||
private handlekeypress = (event: React.KeyboardEvent<HTMLSpanElement>): void => {
|
||||
const { tabs, onTabIndexChange } = this.props;
|
||||
const { activeTabIndex } = this.state;
|
||||
const count = tabs.length;
|
||||
|
||||
const prevTab = () => {
|
||||
this.setIndex((activeTabIndex - 1 + count) % count);
|
||||
onTabIndexChange((activeTabIndex - 1 + count) % count);
|
||||
};
|
||||
const nextTab = () => {
|
||||
this.setIndex((activeTabIndex + 1) % count);
|
||||
onTabIndexChange((activeTabIndex + 1) % count);
|
||||
};
|
||||
|
||||
if (event.key === "ArrowLeft") {
|
||||
prevTab();
|
||||
} else if (event.key === "ArrowRight") {
|
||||
nextTab();
|
||||
}
|
||||
};
|
||||
|
||||
private renderTabTitles(): JSX.Element[] {
|
||||
return this.props.tabs.map((tab: Tab, index: number) => {
|
||||
@@ -79,33 +46,23 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
}
|
||||
|
||||
let className = "toggleSwitch";
|
||||
let ariaselected;
|
||||
let tabindex;
|
||||
if (index === this.props.currentTabIndex) {
|
||||
className += " selectedToggle";
|
||||
ariaselected = true;
|
||||
tabindex = 0;
|
||||
} else {
|
||||
className += " unselectedToggle";
|
||||
ariaselected = false;
|
||||
tabindex = -1;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tab" key={index}>
|
||||
<span
|
||||
<AccessibleElement
|
||||
as="span"
|
||||
className={className}
|
||||
role="tab"
|
||||
onClick={() => this.setActiveTab(index)}
|
||||
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.handlekeypress(event)}
|
||||
onFocus={() => this.setState({ activeTabIndex: index })}
|
||||
role="presentation"
|
||||
onActivated={() => this.setActiveTab(index)}
|
||||
aria-label={`Select tab: ${tab.title}`}
|
||||
aria-selected={ariaselected}
|
||||
tabIndex={tabindex}
|
||||
ref={(element) => (this.tabRefs[index] = element)}
|
||||
>
|
||||
{tab.title}
|
||||
</span>
|
||||
</AccessibleElement>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -120,11 +77,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
|
||||
return (
|
||||
<div className="tabComponentContainer">
|
||||
{!this.props.hideHeader && (
|
||||
<div className="tabs tabSwitch" role="tablist">
|
||||
{this.renderTabTitles()}
|
||||
</div>
|
||||
)}
|
||||
{!this.props.hideHeader && <div className="tabs tabSwitch">{this.renderTabTitles()}</div>}
|
||||
<div className={className}>{currentTabContent.render()}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,12 +23,12 @@ describe("ThroughputInput Pane", () => {
|
||||
});
|
||||
|
||||
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(
|
||||
"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)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -185,48 +185,36 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<div role="radiogroup">
|
||||
<input
|
||||
id="Autoscale-input"
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label="Autoscale database throughput"
|
||||
aria-required={true}
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||
/>
|
||||
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
|
||||
Autoscale
|
||||
</label>
|
||||
<input
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||
/>
|
||||
<span className="throughputInputRadioBtnLabel">Autoscale</span>
|
||||
|
||||
<input
|
||||
id="Manual-input"
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label="Manual database throughput"
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
<label className="throughputInputRadioBtnLabel" htmlFor="Manual-input">
|
||||
Manual
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label="Manual mode"
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
<span className="throughputInputRadioBtnLabel">Manual</span>
|
||||
</Stack>
|
||||
|
||||
{isAutoscaleSelected && (
|
||||
<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{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
>
|
||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="ruDescription">
|
||||
capacity calculator
|
||||
</Link>
|
||||
.
|
||||
|
||||
@@ -344,13 +344,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
styles={[Function]}
|
||||
@@ -630,7 +630,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
}
|
||||
>
|
||||
<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"
|
||||
data-icon-name="Info"
|
||||
role="img"
|
||||
@@ -654,45 +654,40 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
<div
|
||||
className="ms-Stack css-58"
|
||||
>
|
||||
<div
|
||||
<input
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={true}
|
||||
className="throughputInputRadioBtn"
|
||||
key=".0:$.0"
|
||||
role="radiogroup"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="throughputInputRadioBtnLabel"
|
||||
key=".0:$.1"
|
||||
>
|
||||
<input
|
||||
aria-label="Autoscale database throughput"
|
||||
aria-required={true}
|
||||
checked={true}
|
||||
className="throughputInputRadioBtn"
|
||||
id="Autoscale-input"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<label
|
||||
className="throughputInputRadioBtnLabel"
|
||||
htmlFor="Autoscale-input"
|
||||
>
|
||||
Autoscale
|
||||
</label>
|
||||
<input
|
||||
aria-label="Manual database throughput"
|
||||
aria-required={true}
|
||||
checked={false}
|
||||
className="throughputInputRadioBtn"
|
||||
id="Manual-input"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<label
|
||||
className="throughputInputRadioBtnLabel"
|
||||
htmlFor="Manual-input"
|
||||
>
|
||||
Manual
|
||||
</label>
|
||||
</div>
|
||||
Autoscale
|
||||
</span>
|
||||
<input
|
||||
aria-label="Manual mode"
|
||||
aria-required={true}
|
||||
checked={false}
|
||||
className="throughputInputRadioBtn"
|
||||
key=".0:$.2"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="throughputInputRadioBtnLabel"
|
||||
key=".0:$.3"
|
||||
>
|
||||
Manual
|
||||
</span>
|
||||
</div>
|
||||
</Stack>
|
||||
<Stack
|
||||
@@ -702,23 +697,23 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
className="ms-Stack throughputInputSpacing css-59"
|
||||
>
|
||||
<Text
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
aria-label="ruDescription"
|
||||
key=".0:$.0"
|
||||
variant="small"
|
||||
>
|
||||
<span
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
aria-label="ruDescription"
|
||||
className="css-54"
|
||||
>
|
||||
Estimate your required RU/s with
|
||||
|
||||
<StyledLinkBase
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
aria-label="ruDescription"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
target="_blank"
|
||||
>
|
||||
<LinkBase
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
aria-label="ruDescription"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
styles={[Function]}
|
||||
target="_blank"
|
||||
@@ -997,7 +992,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
}
|
||||
>
|
||||
<a
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
aria-label="ruDescription"
|
||||
className="ms-Link root-60"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
onClick={[Function]}
|
||||
@@ -1336,13 +1331,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
styles={[Function]}
|
||||
@@ -1622,7 +1617,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
}
|
||||
>
|
||||
<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"
|
||||
data-icon-name="Info"
|
||||
role="img"
|
||||
|
||||
@@ -195,7 +195,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
</div>
|
||||
{node.children && (
|
||||
<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
|
||||
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
|
||||
|
||||
@@ -100,7 +100,6 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
@@ -252,7 +251,6 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
@@ -354,7 +352,6 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
/>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
@@ -468,7 +465,6 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
@@ -598,7 +594,6 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.treeComponent {
|
||||
.nodeItem {
|
||||
&:focus {
|
||||
outline: 2px @AccentMedium;
|
||||
outline: 1px dashed @AccentMedium;
|
||||
}
|
||||
|
||||
.treeNodeHeader {
|
||||
|
||||
@@ -133,6 +133,7 @@ describe("ContainerSampleGenerator", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
|
||||
// Rejects with error that contains experience
|
||||
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||
});
|
||||
|
||||
|
||||
@@ -93,8 +93,7 @@ export default class Explorer {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
this._isInitializingNotebooks = false;
|
||||
|
||||
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||
this.phoenixClient = new PhoenixClient();
|
||||
useNotebook.subscribe(
|
||||
() => this.refreshCommandBarButtons(),
|
||||
(state) => state.isNotebooksEnabledForAccount
|
||||
@@ -186,7 +185,9 @@ export default class Explorer {
|
||||
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
||||
}
|
||||
|
||||
this.refreshExplorer();
|
||||
if (userContext.apiType !== "Postgres") {
|
||||
this.refreshExplorer();
|
||||
}
|
||||
}
|
||||
|
||||
public async initiateAndRefreshNotebookList(): Promise<void> {
|
||||
@@ -352,7 +353,7 @@ export default class Explorer {
|
||||
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
|
||||
) {
|
||||
const provisionData: IProvisionData = {
|
||||
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
poolId: PoolIdType.DefaultPoolId,
|
||||
};
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
@@ -577,7 +578,7 @@ export default class Explorer {
|
||||
try {
|
||||
await Promise.all(
|
||||
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());
|
||||
if (isNewDatabase) {
|
||||
database.expandDatabase();
|
||||
@@ -1057,10 +1058,6 @@ export default class Explorer {
|
||||
title = "Cassandra Shell";
|
||||
break;
|
||||
|
||||
case ViewModels.TerminalKind.Postgres:
|
||||
title = "PSQL Shell";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Terminal kind: ${kind} not supported");
|
||||
}
|
||||
@@ -1247,11 +1244,9 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async refreshExplorer(): Promise<void> {
|
||||
if (userContext.apiType !== "Postgres") {
|
||||
userContext.authType === AuthType.ResourceToken
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
}
|
||||
userContext.authType === AuthType.ResourceToken
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||
|
||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
||||
|
||||
@@ -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
|
||||
role="img"
|
||||
aria-label="graph"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
@@ -136,7 +135,6 @@ export class GraphVizComponent extends React.Component<GraphVizComponentProps> {
|
||||
<g id="triangleRight">
|
||||
<svg
|
||||
role="img"
|
||||
aria-label="graph"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -17,19 +17,19 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
|
||||
<div className="middlePane">
|
||||
<div className="graphTitle">
|
||||
<span className="paneTitle">Graph</span>
|
||||
<button
|
||||
style={{ border: "none", background: "none" }}
|
||||
<span
|
||||
className="graphExpandCollapseBtn pull-right"
|
||||
onClick={this.props.toggleExpandGraph}
|
||||
role="button"
|
||||
aria-expanded={this.props.isTabsContentExpanded}
|
||||
aria-name="View graph in full screen"
|
||||
tabIndex={0}
|
||||
>
|
||||
<img
|
||||
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
||||
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div className="maingraphContainer">
|
||||
<GraphVizComponent forceGraphParams={this.props.graphVizProps.forceGraphParams} />
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
capabilities: [{ name: "EnableMongo" }],
|
||||
capabilities: [{ name: "EnableTable" }],
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
@@ -38,38 +38,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
);
|
||||
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", () => {
|
||||
|
||||
@@ -51,13 +51,11 @@ export function createStaticCommandBarButtons(
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
|
||||
buttons.push(newCollectionBtn);
|
||||
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
|
||||
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
|
||||
|
||||
if (addSynapseLink) {
|
||||
buttons.push(createDivider());
|
||||
buttons.push(addSynapseLink);
|
||||
}
|
||||
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
|
||||
if (addSynapseLink) {
|
||||
buttons.push(createDivider());
|
||||
buttons.push(addSynapseLink);
|
||||
}
|
||||
|
||||
if (userContext.apiType !== "Tables") {
|
||||
@@ -204,7 +202,6 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
||||
if (showOpenFullScreen) {
|
||||
const label = "Open Full Screen";
|
||||
const fullScreenButton: CommandButtonComponentProps = {
|
||||
id: "openFullScreenBtn",
|
||||
iconSrc: OpenInTabIcon,
|
||||
iconAlt: label,
|
||||
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 {
|
||||
const label = "Reset Workspace";
|
||||
return {
|
||||
@@ -612,7 +587,16 @@ function createStaticCommandBarButtonsForResourceToken(
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -147,9 +147,12 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
<div className="notificationConsoleControls">
|
||||
<Dropdown
|
||||
label="Filter:"
|
||||
role="combobox"
|
||||
selectedKey={this.state.selectedFilter}
|
||||
options={NotificationConsoleComponent.FilterOptions}
|
||||
onChange={this.onFilterSelected.bind(this)}
|
||||
aria-labelledby="consoleFilterLabel"
|
||||
aria-label={this.state.selectedFilter}
|
||||
/>
|
||||
<span className="consoleSplitter" />
|
||||
<span
|
||||
|
||||
@@ -112,6 +112,8 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
className="notificationConsoleControls"
|
||||
>
|
||||
<Dropdown
|
||||
aria-label="All"
|
||||
aria-labelledby="consoleFilterLabel"
|
||||
label="Filter:"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
@@ -134,6 +136,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
role="combobox"
|
||||
selectedKey="All"
|
||||
/>
|
||||
<span
|
||||
@@ -275,6 +278,8 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
className="notificationConsoleControls"
|
||||
>
|
||||
<Dropdown
|
||||
aria-label="All"
|
||||
aria-labelledby="consoleFilterLabel"
|
||||
label="Filter:"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
@@ -297,6 +302,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
role="combobox"
|
||||
selectedKey="All"
|
||||
/>
|
||||
<span
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,7 @@ export class NotebookContainerClient {
|
||||
private scheduleTimerId: NodeJS.Timeout;
|
||||
|
||||
constructor(private onConnectionLost: () => void) {
|
||||
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||
this.phoenixClient = new PhoenixClient();
|
||||
this.retryOptions = {
|
||||
retries: Notebook.retryAttempts,
|
||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||
|
||||
@@ -124,9 +124,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
}
|
||||
|
||||
const firstWriteLocation =
|
||||
userContext.apiType === "Postgres"
|
||||
? databaseAccount?.location
|
||||
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
||||
databaseAccount?.properties?.writeLocations &&
|
||||
databaseAccount?.properties?.writeLocations[0]?.locationName.toLowerCase();
|
||||
const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
try {
|
||||
@@ -308,16 +307,13 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
let isPhoenixFeatures = false;
|
||||
|
||||
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
||||
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||
const phoenixClient = new PhoenixClient();
|
||||
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
||||
|
||||
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
||||
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
||||
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
|
||||
isPhoenixFeatures =
|
||||
isPublicInternetAllowed &&
|
||||
// phoenix needs to be enabled for Postgres accounts since the PSQL shell requires phoenix containers
|
||||
(userContext.features.phoenixFeatures === true || userContext.apiType === "Postgres");
|
||||
isPhoenixFeatures = isPublicInternetAllowed && userContext.features.phoenixFeatures === true;
|
||||
} else {
|
||||
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
jest.mock("../hooks/useFullScreenURLs");
|
||||
import "@testing-library/jest-dom";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
||||
import { OpenFullScreen } from "./OpenFullScreen";
|
||||
|
||||
it("renders the correct URLs", () => {
|
||||
(useFullScreenURLs as jest.Mock).mockReturnValue({
|
||||
readWrite: "read and write url",
|
||||
read: "read only url",
|
||||
});
|
||||
|
||||
render(<OpenFullScreen />);
|
||||
expect(screen.getByText("Open")).toBeDefined();
|
||||
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");
|
||||
});
|
||||
|
||||
@@ -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 { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
||||
|
||||
export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
|
||||
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
|
||||
const result = useFullScreenURLs();
|
||||
if (!result) {
|
||||
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
|
||||
}
|
||||
|
||||
const readWriteUrl = `https://cosmos.azure.com/?key=${result.readWrite}`;
|
||||
const readUrl = `https://cosmos.azure.com/?key=${result.read}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ padding: "34px" }}>
|
||||
<Stack tokens={{ childrenGap: 10 }}>
|
||||
<Text>
|
||||
Open this database account in a new browser tab with Cosmos DB Explorer. You can connect using your
|
||||
Microsoft account or a connection string.
|
||||
</Text>
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||
<PrimaryButton
|
||||
onClick={() => {
|
||||
window.open("https://cosmos.azure.com/", "_blank");
|
||||
}}
|
||||
text="Open"
|
||||
iconProps={{ iconName: "OpenInNewWindow" }}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack tokens={{ childrenGap: 10 }}>
|
||||
<Text>
|
||||
Open this database account in a new browser tab with Cosmos DB Explorer. Or copy the read-write or read only
|
||||
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>
|
||||
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||
<DefaultButton
|
||||
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
|
||||
onClick={() => {
|
||||
copyToClipboard(readWriteUrl);
|
||||
setIsReadWriteUrlCopy(true);
|
||||
}}
|
||||
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
/>
|
||||
<PrimaryButton
|
||||
onClick={() => {
|
||||
window.open(readWriteUrl, "_blank");
|
||||
}}
|
||||
text="Open"
|
||||
iconProps={{ iconName: "OpenInNewWindow" }}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
<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"
|
||||
iconProps={{ iconName: "OpenInNewWindow" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -89,10 +89,9 @@ export interface AddCollectionPanelState {
|
||||
enableIndexing: boolean;
|
||||
isSharded: boolean;
|
||||
partitionKey: string;
|
||||
subPartitionKeys: string[];
|
||||
enableDedicatedThroughput: boolean;
|
||||
createMongoWildCardIndex: boolean;
|
||||
useHashV1: boolean;
|
||||
useHashV2: boolean;
|
||||
enableAnalyticalStore: boolean;
|
||||
uniqueKeys: string[];
|
||||
errorMessage: string;
|
||||
@@ -122,11 +121,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
enableIndexing: true,
|
||||
isSharded: userContext.apiType !== "Tables",
|
||||
partitionKey: this.getPartitionKey(),
|
||||
subPartitionKeys: [],
|
||||
enableDedicatedThroughput: false,
|
||||
createMongoWildCardIndex:
|
||||
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
|
||||
useHashV1: false,
|
||||
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
|
||||
useHashV2: false,
|
||||
enableAnalyticalStore: false,
|
||||
uniqueKeys: [],
|
||||
errorMessage: "",
|
||||
@@ -262,46 +259,37 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
true
|
||||
).toLocaleLowerCase()}.`}
|
||||
>
|
||||
<Icon
|
||||
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()}.`}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<div role="radiogroup">
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.createNewDatabase}
|
||||
aria-label="Create new database"
|
||||
aria-checked={this.state.createNewDatabase}
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="databaseCreateNew"
|
||||
tabIndex={0}
|
||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">Create new</span>
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.createNewDatabase}
|
||||
aria-label="Create new database"
|
||||
aria-checked={this.state.createNewDatabase}
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="databaseCreateNew"
|
||||
tabIndex={0}
|
||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">Create new</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.createNewDatabase}
|
||||
aria-label="Use existing database"
|
||||
aria-checked={!this.state.createNewDatabase}
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">Use existing</span>
|
||||
</div>
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.createNewDatabase}
|
||||
aria-label="Use existing database"
|
||||
aria-checked={!this.state.createNewDatabase}
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">Use existing</span>
|
||||
</Stack>
|
||||
|
||||
{this.state.createNewDatabase && (
|
||||
@@ -347,14 +335,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
true
|
||||
).toLocaleLowerCase()} within the database.`}
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||
true
|
||||
).toLocaleLowerCase()} within the database.`}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -402,13 +383,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
>
|
||||
<Icon
|
||||
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.`}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</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."
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
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."
|
||||
}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -545,12 +513,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={this.getPartitionKeyTooltipText()}
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={this.getPartitionKeyTooltipText()}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
billed in addition to the throughput amount you provisioned at the database level.`}
|
||||
>
|
||||
<Icon
|
||||
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.`}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -720,18 +602,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={
|
||||
"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."
|
||||
}
|
||||
content="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."
|
||||
>
|
||||
<Icon
|
||||
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."
|
||||
}
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -794,48 +669,40 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={this.getAnalyticalStorageTooltipContent()}
|
||||
>
|
||||
<Icon
|
||||
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."
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<div role="radiogroup">
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.enableAnalyticalStore}
|
||||
disabled={!this.isSynapseLinkEnabled()}
|
||||
aria-label="Enable analytical store"
|
||||
aria-checked={this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="enableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">On</span>
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.enableAnalyticalStore}
|
||||
disabled={!this.isSynapseLinkEnabled()}
|
||||
aria-label="Enable analytical store"
|
||||
aria-checked={this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="enableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">On</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.enableAnalyticalStore}
|
||||
disabled={!this.isSynapseLinkEnabled()}
|
||||
aria-label="Disable analytical store"
|
||||
aria-checked={!this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="disableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">Off</span>
|
||||
</div>
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.enableAnalyticalStore}
|
||||
disabled={!this.isSynapseLinkEnabled()}
|
||||
aria-label="Disable analytical store"
|
||||
aria-checked={!this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="disableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">Off</span>
|
||||
</Stack>
|
||||
|
||||
{!this.isSynapseLinkEnabled() && (
|
||||
@@ -868,7 +735,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
}}
|
||||
>
|
||||
<Stack className="panelGroupSpacing" id="collapsibleSectionContent">
|
||||
{isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && (
|
||||
{isCapabilityEnabled("EnableMongo") && (
|
||||
<Stack className="panelGroupSpacing">
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
@@ -879,12 +746,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
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."
|
||||
>
|
||||
<Icon
|
||||
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."
|
||||
/>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -904,29 +766,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
)}
|
||||
|
||||
{userContext.apiType === "SQL" && (
|
||||
<Stack className="panelGroupSpacing">
|
||||
<Checkbox
|
||||
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
|
||||
checked={this.state.useHashV1}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
checkbox: { width: 12, height: 12 },
|
||||
label: { padding: 0, alignItems: "center", wordWrap: "break-word", whiteSpace: "break-spaces" },
|
||||
}}
|
||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||
this.setState({ useHashV1: isChecked, subPartitionKeys: [] })
|
||||
}
|
||||
/>
|
||||
<Text variant="small">
|
||||
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> To ensure compatibility with
|
||||
older SDKs, the created container will use a legacy partitioning scheme that supports partition
|
||||
key values of size only up to 101 bytes. If this is enabled, you will not be able to use
|
||||
hierarchical partition keys.{" "}
|
||||
<Link href="https://aka.ms/cosmos-large-pk" target="_blank">
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
<Checkbox
|
||||
label="My partition key is larger than 101 bytes"
|
||||
checked={this.state.useHashV2}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
checkbox: { width: 12, height: 12 },
|
||||
label: { padding: 0, alignItems: "center" },
|
||||
}}
|
||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||
this.setState({ useHashV2: isChecked })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</CollapsibleSectionComponent>
|
||||
@@ -981,20 +832,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
|
||||
}
|
||||
|
||||
private getPartitionKeyPlaceHolder(index?: number): string {
|
||||
private getPartitionKeyPlaceHolder(): string {
|
||||
switch (userContext.apiType) {
|
||||
case "Mongo":
|
||||
return "e.g., categoryId";
|
||||
return "e.g., address.zipCode";
|
||||
case "Gremlin":
|
||||
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:
|
||||
return "e.g., /address/zipCode";
|
||||
}
|
||||
@@ -1116,7 +959,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
return userContext.apiType === "SQL" ? "/pk" : "pk";
|
||||
}
|
||||
if (this.props.isQuickstart) {
|
||||
return userContext.apiType === "SQL" ? "/categoryId" : "categoryId";
|
||||
return userContext.apiType === "SQL" ? "/address" : "address";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -1320,16 +1163,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
}
|
||||
|
||||
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
|
||||
? {
|
||||
paths: [
|
||||
partitionKeyString,
|
||||
...(userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0
|
||||
? this.state.subPartitionKeys
|
||||
: []),
|
||||
],
|
||||
kind: userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0 ? "MultiHash" : "Hash",
|
||||
paths: [partitionKeyString],
|
||||
kind: "Hash",
|
||||
version: partitionKeyVersion,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
@@ -89,8 +89,8 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
}
|
||||
} catch (error) {
|
||||
setLoadingFalse();
|
||||
setFormError(error);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setFormError(errorMessage);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.DeleteDatabase,
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
|
||||
@@ -48,7 +48,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
||||
)}
|
||||
</Text>
|
||||
{showErrorDetails && (
|
||||
<a className="paneErrorLink" role="link" onClick={expandConsole} tabIndex={0} onKeyPress={expandConsole}>
|
||||
<a className="paneErrorLink" role="link" onClick={expandConsole}>
|
||||
More details
|
||||
</a>
|
||||
)}
|
||||
|
||||
@@ -21,11 +21,6 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
const [customItemPerPage, setCustomItemPerPage] = useState<number>(
|
||||
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>(
|
||||
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled)
|
||||
? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
||||
@@ -55,7 +50,6 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage
|
||||
);
|
||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
||||
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
|
||||
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||
|
||||
@@ -191,25 +185,6 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
</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 && (
|
||||
<div className="settingsSection">
|
||||
<div className="settingsSectionPart">
|
||||
|
||||
@@ -97,35 +97,6 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||
</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
|
||||
className="settingsSection"
|
||||
>
|
||||
@@ -211,35 +182,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||
<div
|
||||
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
|
||||
className="settingsSection"
|
||||
>
|
||||
|
||||
@@ -14,7 +14,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
|
||||
@@ -242,11 +242,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
submitButtonText: getButtonLabel(userContext.apiType),
|
||||
onSubmit,
|
||||
};
|
||||
const handlekeypressaddentity = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (event.key === "Enter" || event.key === "Space") {
|
||||
addNewEntity();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<RightPaneForm {...props}>
|
||||
@@ -289,13 +284,7 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
);
|
||||
})}
|
||||
{userContext.apiType !== "Cassandra" && (
|
||||
<Stack
|
||||
horizontal
|
||||
onClick={addNewEntity}
|
||||
className="addButtonEntiy"
|
||||
tabIndex={0}
|
||||
onKeyPress={handlekeypressaddentity}
|
||||
>
|
||||
<Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
|
||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
||||
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -29,14 +29,10 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
className="addButtonEntiy"
|
||||
horizontal={true}
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="ms-Stack addButtonEntiy css-53"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
tabIndex={0}
|
||||
>
|
||||
<StyledImageBase
|
||||
alt="Add Entity"
|
||||
|
||||
@@ -31,7 +31,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
directionalHint={4}
|
||||
>
|
||||
<Icon
|
||||
ariaLabel="A database is analogous to a namespace. It is the unit of management for a set of containers."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
@@ -42,43 +41,39 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<div
|
||||
role="radiogroup"
|
||||
<input
|
||||
aria-checked={true}
|
||||
aria-label="Create new database"
|
||||
checked={true}
|
||||
className="panelRadioBtn"
|
||||
id="databaseCreateNew"
|
||||
name="databaseType"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
<input
|
||||
aria-checked={true}
|
||||
aria-label="Create new database"
|
||||
checked={true}
|
||||
className="panelRadioBtn"
|
||||
id="databaseCreateNew"
|
||||
name="databaseType"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
Create new
|
||||
</span>
|
||||
<input
|
||||
aria-checked={false}
|
||||
aria-label="Use existing database"
|
||||
checked={false}
|
||||
className="panelRadioBtn"
|
||||
name="databaseType"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
Use existing
|
||||
</span>
|
||||
</div>
|
||||
Create new
|
||||
</span>
|
||||
<input
|
||||
aria-checked={false}
|
||||
aria-label="Use existing database"
|
||||
checked={false}
|
||||
className="panelRadioBtn"
|
||||
name="databaseType"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
Use existing
|
||||
</span>
|
||||
</Stack>
|
||||
<Stack
|
||||
className="panelGroupSpacing"
|
||||
@@ -129,7 +124,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
directionalHint={4}
|
||||
>
|
||||
<Icon
|
||||
ariaLabel="Throughput configured at the database level will be shared across all containers within the database."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
@@ -169,10 +163,8 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
directionalHint={4}
|
||||
>
|
||||
<Icon
|
||||
ariaLabel="Unique identifier for the container and used for id-based routing through REST and all SDKs."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</StyledTooltipHostBase>
|
||||
@@ -214,7 +206,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
directionalHint={4}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
@@ -232,36 +223,13 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
id="addCollection-partitionKeyValue"
|
||||
onChange={[Function]}
|
||||
pattern=".*"
|
||||
placeholder="Required - first partition key e.g., /TenantId"
|
||||
placeholder="e.g., /address/zipCode"
|
||||
required={true}
|
||||
size={40}
|
||||
title=""
|
||||
type="text"
|
||||
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
|
||||
@@ -278,7 +246,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
directionalHint={4}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
@@ -336,7 +303,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
directionalHint={4}
|
||||
>
|
||||
<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"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
@@ -347,46 +313,42 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<div
|
||||
role="radiogroup"
|
||||
<input
|
||||
aria-checked={false}
|
||||
aria-label="Enable analytical store"
|
||||
checked={false}
|
||||
className="panelRadioBtn"
|
||||
disabled={true}
|
||||
id="enableAnalyticalStoreBtn"
|
||||
name="analyticalStore"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
<input
|
||||
aria-checked={false}
|
||||
aria-label="Enable analytical store"
|
||||
checked={false}
|
||||
className="panelRadioBtn"
|
||||
disabled={true}
|
||||
id="enableAnalyticalStoreBtn"
|
||||
name="analyticalStore"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
On
|
||||
</span>
|
||||
<input
|
||||
aria-checked={true}
|
||||
aria-label="Disable analytical store"
|
||||
checked={true}
|
||||
className="panelRadioBtn"
|
||||
disabled={true}
|
||||
id="disableAnalyticalStoreBtn"
|
||||
name="analyticalStore"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
Off
|
||||
</span>
|
||||
</div>
|
||||
On
|
||||
</span>
|
||||
<input
|
||||
aria-checked={true}
|
||||
aria-label="Disable analytical store"
|
||||
checked={true}
|
||||
className="panelRadioBtn"
|
||||
disabled={true}
|
||||
id="disableAnalyticalStoreBtn"
|
||||
name="analyticalStore"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="panelRadioBtnLabel"
|
||||
>
|
||||
Off
|
||||
</span>
|
||||
</Stack>
|
||||
<Stack
|
||||
className="panelGroupSpacing"
|
||||
@@ -434,49 +396,26 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
className="panelGroupSpacing"
|
||||
id="collapsibleSectionContent"
|
||||
>
|
||||
<Stack
|
||||
className="panelGroupSpacing"
|
||||
>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"checkbox": Object {
|
||||
"height": 12,
|
||||
"width": 12,
|
||||
},
|
||||
"label": Object {
|
||||
"alignItems": "center",
|
||||
"padding": 0,
|
||||
"whiteSpace": "break-spaces",
|
||||
"wordWrap": "break-word",
|
||||
},
|
||||
"text": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
label="My partition key is larger than 101 bytes"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"checkbox": Object {
|
||||
"height": 12,
|
||||
"width": 12,
|
||||
},
|
||||
"label": Object {
|
||||
"alignItems": "center",
|
||||
"padding": 0,
|
||||
},
|
||||
"text": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
variant="small"
|
||||
>
|
||||
<Icon
|
||||
className="removeIcon"
|
||||
iconName="InfoSolid"
|
||||
tabIndex={0}
|
||||
/>
|
||||
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://aka.ms/cosmos-large-pk"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</CollapsibleSectionComponent>
|
||||
</div>
|
||||
|
||||
@@ -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;`;
|
||||
@@ -94,7 +94,7 @@ const getDescriptionText = (page: number): string => {
|
||||
case 1:
|
||||
return "Azure Cosmos DB is a fully managed NoSQL database service for modern app development. ";
|
||||
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:
|
||||
return "Already have an existing app? Connect your database to an app, or tooling of your choice from Data Explorer.";
|
||||
default:
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
@@ -11,17 +11,6 @@ import {
|
||||
Text,
|
||||
TextField,
|
||||
} 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 Youtube from "react-youtube";
|
||||
import Pivot1SelectedIcon from "../../../images/Pivot1_selected.svg";
|
||||
@@ -46,6 +35,24 @@ enum GuideSteps {
|
||||
|
||||
export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
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 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={{ flexGrow: 1, padding: "0 20px", overflow: "auto" }}>
|
||||
<Text variant="xxLarge">Quick start guide</Text>
|
||||
<Text variant="medium">Gettings started in Cosmos DB</Text>
|
||||
{currentStep < 5 && (
|
||||
<Pivot
|
||||
style={{ marginTop: 10, width: "100%" }}
|
||||
selectedKey={GuideSteps[currentStep]}
|
||||
onLinkClick={(item?: PivotItem) => setCurrentStep(Object.values(GuideSteps).indexOf(item.props.itemKey))}
|
||||
>
|
||||
<Pivot style={{ marginTop: 10, width: "100%" }} selectedKey={GuideSteps[currentStep]}>
|
||||
<PivotItem
|
||||
headerText="Login"
|
||||
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)}
|
||||
itemKey={GuideSteps[0]}
|
||||
onClick={() => {
|
||||
setCurrentStep(0);
|
||||
}}
|
||||
onClick={() => setCurrentStep(0)}
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>
|
||||
This tutorial guides you to create and query distributed tables using a sample dataset.
|
||||
<br />
|
||||
<br />
|
||||
To begin, please enter the cluster's password in the PostgreSQL terminal.
|
||||
<br />
|
||||
<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.
|
||||
This tutorial walks you through the most essential Cosmos DB PostgreSQL statements that will be used
|
||||
in the PostgreSQL shell (on the right). You can also choose to go through this quick start by
|
||||
connecting to PGAdmin or other tooling of your choice. <br />
|
||||
<br /> Before you can interact with your data using PGShell, you will need to login - please follow
|
||||
instructions on the right to enter your password
|
||||
</Text>
|
||||
<Youtube videoId="nT64dFSfiUo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "80%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -130,28 +129,19 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
onClick={() => setCurrentStep(1)}
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>Let's create two tables github_users and github_events in “cosmosdb_tutorial” schema.</Text>
|
||||
<DefaultButton
|
||||
style={{ marginTop: 16, width: 150 }}
|
||||
onClick={() => useTerminal.getState().sendMessage(newTableCommand)}
|
||||
>
|
||||
Create new table
|
||||
</DefaultButton>
|
||||
<Text>
|
||||
After logging in, let’s create some new tables for storing data. We will start with two sample tables
|
||||
- one for storing github users and one for storing github events
|
||||
</Text>
|
||||
<DefaultButton style={{ marginTop: 16, width: 110 }}>New table</DefaultButton>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="newTableCommand"
|
||||
multiline
|
||||
rows={5}
|
||||
rows={6}
|
||||
readOnly
|
||||
defaultValue={newTableCommandForDisplay}
|
||||
styles={{
|
||||
root: { width: "90%" },
|
||||
field: {
|
||||
backgroundColor: "#EEEEEE",
|
||||
fontFamily:
|
||||
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
|
||||
},
|
||||
}}
|
||||
defaultValue={newTableCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -160,7 +150,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
onClick={() => onCopyBtnClicked("#newTableCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="il_sA6U1WcY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -171,32 +161,21 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>
|
||||
Let’s distribute the two tables using the create_distributed_table() function.
|
||||
Congratulations, you have now created your first 2 tables.
|
||||
<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>
|
||||
<DefaultButton
|
||||
style={{ marginTop: 16, width: 200 }}
|
||||
onClick={() => useTerminal.getState().sendMessage(distributeTableCommand)}
|
||||
>
|
||||
Create distributed table
|
||||
</DefaultButton>
|
||||
<DefaultButton style={{ marginTop: 16, width: 150 }}>Distribute table</DefaultButton>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="distributeTableCommand"
|
||||
multiline
|
||||
rows={5}
|
||||
rows={2}
|
||||
readOnly
|
||||
defaultValue={distributeTableCommandForDisplay}
|
||||
styles={{
|
||||
root: { width: "90%" },
|
||||
field: {
|
||||
backgroundColor: "#EEEEEE",
|
||||
fontFamily:
|
||||
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
|
||||
},
|
||||
}}
|
||||
defaultValue={distributeTableCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -205,7 +184,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="kCCDRRrN1r0" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -215,28 +194,22 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
onClick={() => setCurrentStep(3)}
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>Let's load the two tables with a sample dataset generated from the GitHub API.</Text>
|
||||
<DefaultButton
|
||||
style={{ marginTop: 16, width: 110 }}
|
||||
onClick={() => useTerminal.getState().sendMessage(loadDataCommand)}
|
||||
>
|
||||
Load data
|
||||
</DefaultButton>
|
||||
<Text>
|
||||
We're ready to fill the tables with sample data.
|
||||
<br />
|
||||
<br />
|
||||
For this quick start, we'll use a dataset previously captured from the GitHub API. Run the
|
||||
command below to load the data
|
||||
</Text>
|
||||
<DefaultButton style={{ marginTop: 16, width: 110 }}>Load data</DefaultButton>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="loadDataCommand"
|
||||
multiline
|
||||
rows={5}
|
||||
rows={4}
|
||||
readOnly
|
||||
defaultValue={loadDataCommandForDisplay}
|
||||
styles={{
|
||||
root: { width: "90%" },
|
||||
field: {
|
||||
backgroundColor: "#EEEEEE",
|
||||
fontFamily:
|
||||
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
|
||||
},
|
||||
}}
|
||||
defaultValue={loadDataCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -245,7 +218,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
onClick={() => onCopyBtnClicked("#loadDataCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="XSMEE2tujEk" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -256,29 +229,19 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>
|
||||
Congratulations on creating and distributing your tables. Now, it'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. Let’s try a
|
||||
query.
|
||||
</Text>
|
||||
<DefaultButton
|
||||
style={{ marginTop: 16, width: 115 }}
|
||||
onClick={() => useTerminal.getState().sendMessage(queryCommand)}
|
||||
>
|
||||
Try queries
|
||||
</DefaultButton>
|
||||
<DefaultButton style={{ marginTop: 16, width: 110 }}>Try query</DefaultButton>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="queryCommand"
|
||||
multiline
|
||||
rows={5}
|
||||
rows={6}
|
||||
readOnly
|
||||
defaultValue={queryCommandForDisplay}
|
||||
styles={{
|
||||
root: { width: "90%" },
|
||||
field: {
|
||||
backgroundColor: "#EEEEEE",
|
||||
fontFamily:
|
||||
"Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New",
|
||||
},
|
||||
}}
|
||||
defaultValue={queryCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -287,7 +250,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
onClick={() => onCopyBtnClicked("#queryCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="k_EanjMtaPg" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
@@ -295,7 +258,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||
{currentStep === 5 && (
|
||||
<Stack style={{ margin: "auto" }} horizontalAlign="center">
|
||||
<Image src={CompleteIcon} />
|
||||
<Text variant="mediumPlus" style={{ fontWeight: 900, marginTop: 7 }}>
|
||||
<Text variant="mediumPlus" style={{ fontWeight: 600, marginTop: 7 }}>
|
||||
You are all set!
|
||||
</Text>
|
||||
<Text variant="mediumPlus" style={{ marginTop: 8 }}>
|
||||
|
||||
@@ -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 { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import React from "react";
|
||||
@@ -18,11 +18,6 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
let totalSteps = 9;
|
||||
if (userContext.isTryCosmosDBSubscription) {
|
||||
totalSteps = 10;
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
return isSampleDBExpanded ? (
|
||||
@@ -38,7 +33,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
},
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 1 of " + totalSteps}
|
||||
footerContent="Step 1 of 8"
|
||||
>
|
||||
Start viewing and working with your data by opening Documents under Data
|
||||
</TeachingBubble>
|
||||
@@ -60,7 +55,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(1),
|
||||
}}
|
||||
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
|
||||
so.
|
||||
@@ -83,7 +78,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(2),
|
||||
}}
|
||||
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
|
||||
drivers to do so.
|
||||
@@ -104,9 +99,9 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(3),
|
||||
}}
|
||||
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.
|
||||
</TeachingBubble>
|
||||
);
|
||||
@@ -125,7 +120,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(4),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 5 of " + totalSteps}
|
||||
footerContent="Step 5 of 8"
|
||||
>
|
||||
Change throughput provisioned to your collection according to your needs
|
||||
</TeachingBubble>
|
||||
@@ -145,7 +140,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(5),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 6 of " + totalSteps}
|
||||
footerContent="Step 6 of 8"
|
||||
>
|
||||
Use the indexing policy editor to create and edit your indexes.
|
||||
</TeachingBubble>
|
||||
@@ -165,54 +160,12 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(6),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 7 of " + totalSteps}
|
||||
footerContent="Step 7 of 8"
|
||||
>
|
||||
Visualize your data, store queries in an interactive document
|
||||
</TeachingBubble>
|
||||
);
|
||||
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're ready, upgrade to production.
|
||||
</TeachingBubble>
|
||||
);
|
||||
case 10:
|
||||
return (
|
||||
<TeachingBubble
|
||||
headline="Congratulations!"
|
||||
@@ -227,10 +180,10 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
secondaryButtonProps={{
|
||||
text: "Previous",
|
||||
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(8)),
|
||||
onClick: () => setStep(7),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step " + totalSteps + " of " + totalSteps}
|
||||
footerContent="Step 8 of 8"
|
||||
>
|
||||
<Stack>
|
||||
<Text style={{ color: "white" }}>
|
||||
|
||||
@@ -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 { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import React from "react";
|
||||
@@ -17,10 +17,6 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
if (userContext.apiType !== "SQL") {
|
||||
return <></>;
|
||||
}
|
||||
let totalSteps = 8;
|
||||
if (userContext.isTryCosmosDBSubscription) {
|
||||
totalSteps = 9;
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
@@ -37,7 +33,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
},
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 1 of " + totalSteps}
|
||||
footerContent="Step 1 of 7"
|
||||
>
|
||||
Start viewing and working with your data by opening Items under Data
|
||||
</TeachingBubble>
|
||||
@@ -59,7 +55,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(1),
|
||||
}}
|
||||
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
|
||||
function
|
||||
@@ -82,7 +78,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(2),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 3 of " + totalSteps}
|
||||
footerContent="Step 3 of 7"
|
||||
>
|
||||
Add new item by copy / pasting JSON; or uploading a JSON
|
||||
</TeachingBubble>
|
||||
@@ -102,7 +98,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(3),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 4 of " + totalSteps}
|
||||
footerContent="Step 4 of 7"
|
||||
>
|
||||
Query your data using either the filter function or new query.
|
||||
</TeachingBubble>
|
||||
@@ -122,7 +118,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(4),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 5 of " + totalSteps}
|
||||
footerContent="Step 5 of 7"
|
||||
>
|
||||
Change throughput provisioned to your container according to your needs
|
||||
</TeachingBubble>
|
||||
@@ -142,54 +138,12 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(5),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step 6 of " + totalSteps}
|
||||
footerContent="Step 6 of 7"
|
||||
>
|
||||
Visualize your data, store queries in an interactive document
|
||||
</TeachingBubble>
|
||||
);
|
||||
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're ready, upgrade to production.
|
||||
</TeachingBubble>
|
||||
);
|
||||
case 9:
|
||||
return (
|
||||
<TeachingBubble
|
||||
headline="Congratulations!"
|
||||
@@ -204,10 +158,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
secondaryButtonProps={{
|
||||
text: "Previous",
|
||||
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(7)),
|
||||
onClick: () => setStep(6),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent={"Step " + totalSteps + " of " + totalSteps}
|
||||
footerContent="Step 7 of 7"
|
||||
>
|
||||
<Stack>
|
||||
<Text style={{ color: "white" }}>
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
padding-right: 16px;
|
||||
max-width: 1168px;
|
||||
|
||||
> * {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> .title {
|
||||
position: relative; // To attach FeaturePanelLauncher as absolute
|
||||
color: @BaseHigh;
|
||||
@@ -36,7 +40,6 @@
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 40px auto;
|
||||
width: 84%;
|
||||
|
||||
> .mainButton {
|
||||
min-width: 124px;
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
TeachingBubbleContent,
|
||||
Text,
|
||||
} from "@fluentui/react";
|
||||
import { sendMessage } from "Common/MessageHandler";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { TerminalKind } from "Contracts/ViewModels";
|
||||
import { useCarousel } from "hooks/useCarousel";
|
||||
import { usePostgres } from "hooks/usePostgres";
|
||||
@@ -93,12 +91,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
() => this.setState({}),
|
||||
(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">
|
||||
<div className="splashScreenContainer">
|
||||
<div className="splashScreen">
|
||||
<div
|
||||
className="title"
|
||||
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"}
|
||||
<div className="title">
|
||||
{userContext.apiType === "Postgres" ? "Welcome to Cosmos DB - PostgreSQL" : "Welcome to Cosmos DB"}
|
||||
<FeaturePanelLauncher />
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
@@ -135,36 +118,26 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
: "Globally distributed, multi-model database service for any scale"}
|
||||
</div>
|
||||
<div className="mainButtonsContainer">
|
||||
{userContext.apiType === "Postgres" &&
|
||||
usePostgres.getState().showPostgreTeachingBubble &&
|
||||
!usePostgres.getState().showResetPasswordBubble && (
|
||||
<TeachingBubble
|
||||
headline="New to Cosmos DB PGSQL?"
|
||||
target={"#mainButton-quickstartDescription"}
|
||||
hasCloseButton
|
||||
onDismiss={() => usePostgres.getState().setShowPostgreTeachingBubble(false)}
|
||||
calloutProps={{
|
||||
directionalHint: DirectionalHint.rightCenter,
|
||||
directionalHintFixed: true,
|
||||
preventDismissOnLostFocus: true,
|
||||
preventDismissOnResize: true,
|
||||
preventDismissOnScroll: true,
|
||||
}}
|
||||
primaryButtonProps={{
|
||||
text: "Get started",
|
||||
onClick: () => {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.Quickstart);
|
||||
usePostgres.getState().setShowPostgreTeachingBubble(false);
|
||||
},
|
||||
}}
|
||||
>
|
||||
Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you
|
||||
can find sample data, query.
|
||||
</TeachingBubble>
|
||||
)}
|
||||
{userContext.apiType === "Postgres" && usePostgres.getState().showPostgreTeachingBubble && (
|
||||
<TeachingBubble
|
||||
headline="New to Cosmos DB PGSQL?"
|
||||
target={"#quickstartDescription"}
|
||||
hasCloseButton
|
||||
onDismiss={() => usePostgres.getState().setShowPostgreTeachingBubble(false)}
|
||||
primaryButtonProps={{
|
||||
text: "Get started",
|
||||
onClick: () => {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.Quickstart);
|
||||
usePostgres.getState().setShowPostgreTeachingBubble(false);
|
||||
},
|
||||
}}
|
||||
>
|
||||
Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you can
|
||||
find sample data, query.
|
||||
</TeachingBubble>
|
||||
)}
|
||||
{mainItems.map((item) => (
|
||||
<Stack
|
||||
id={`mainButton-${item.id}`}
|
||||
horizontal
|
||||
className="mainButton focusable"
|
||||
key={`${item.title}`}
|
||||
@@ -188,36 +161,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</div>
|
||||
</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't changed your password yet, change it now.
|
||||
</TeachingBubble>
|
||||
)}
|
||||
</div>
|
||||
{useCarousel.getState().showCoachMark && (
|
||||
<Coachmark
|
||||
@@ -248,8 +191,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</Coachmark>
|
||||
)}
|
||||
{userContext.apiType === "Postgres" ? (
|
||||
<Stack horizontal style={{ margin: "0 auto", width: "84%" }} tokens={{ childrenGap: 32 }}>
|
||||
<Stack style={{ width: "33%" }}>
|
||||
<Stack horizontal style={{ margin: "0 auto" }} tokens={{ childrenGap: "15%" }}>
|
||||
<Stack>
|
||||
<Text
|
||||
variant="large"
|
||||
style={{
|
||||
@@ -261,7 +204,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</Text>
|
||||
{this.getNextStepItems()}
|
||||
</Stack>
|
||||
<Stack style={{ width: "33%" }}>
|
||||
<Stack>
|
||||
<Text
|
||||
variant="large"
|
||||
style={{
|
||||
@@ -273,7 +216,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</Text>
|
||||
{this.getTipsAndLearnMoreItems()}
|
||||
</Stack>
|
||||
<Stack style={{ width: "33%" }}></Stack>
|
||||
</Stack>
|
||||
) : (
|
||||
<div className="moreStuffContainer">
|
||||
@@ -311,11 +253,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
public createMainItems(): SplashScreenItem[] {
|
||||
const heroes: SplashScreenItem[] = [];
|
||||
|
||||
if (
|
||||
userContext.apiType === "SQL" ||
|
||||
userContext.apiType === "Mongo" ||
|
||||
(userContext.apiType === "Postgres" && !userContext.isReplica)
|
||||
) {
|
||||
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Postgres") {
|
||||
const launchQuickstartBtn = {
|
||||
id: "quickstartDescription",
|
||||
iconSrc: QuickStartIcon,
|
||||
@@ -344,7 +282,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
iconSrc: PowerShellIcon,
|
||||
title: "PostgreSQL Shell",
|
||||
description: "Create table and interact with data using PostgreSQL’s shell interface",
|
||||
onClick: () => this.container.openNotebookTerminal(TerminalKind.Postgres),
|
||||
onClick: () => this.container.openNotebookTerminal(TerminalKind.Mongo),
|
||||
};
|
||||
heroes.push(postgreShellBtn);
|
||||
} else {
|
||||
@@ -362,11 +300,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
|
||||
const connectBtn = {
|
||||
iconSrc: ConnectIcon,
|
||||
title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect",
|
||||
title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
|
||||
description:
|
||||
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),
|
||||
};
|
||||
heroes.push(connectBtn);
|
||||
@@ -450,7 +388,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
{
|
||||
link: "https://aka.ms/mongodbintro",
|
||||
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",
|
||||
@@ -507,7 +445,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
{
|
||||
link: "https://aka.ms/tableintro",
|
||||
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",
|
||||
@@ -516,8 +454,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/tablefaq",
|
||||
title: "Azure Cosmos DB for Table FAQs",
|
||||
description: "Common questions about Azure Cosmos DB for Table",
|
||||
title: "Table API FAQs",
|
||||
description: "Common questions about the Table API",
|
||||
},
|
||||
];
|
||||
break;
|
||||
@@ -535,7 +473,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
<Image src={LinkIcon} alt=" " />
|
||||
<Image src={LinkIcon} />
|
||||
</Stack>
|
||||
<Text>{item.description}</Text>
|
||||
</Stack>
|
||||
@@ -570,17 +508,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
}
|
||||
|
||||
private getLearningResourceItems(): JSX.Element {
|
||||
interface item {
|
||||
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[];
|
||||
let items: { link: string; title: string; description: string }[];
|
||||
switch (userContext.apiType) {
|
||||
case "SQL":
|
||||
case "Postgres":
|
||||
@@ -590,7 +518,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Get Started using an 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",
|
||||
title: "Migrate Your Data",
|
||||
@@ -610,7 +542,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Getting Started Guide",
|
||||
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;
|
||||
case "Cassandra":
|
||||
@@ -620,7 +556,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Create a Container",
|
||||
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",
|
||||
title: "Provision Throughput",
|
||||
@@ -640,7 +580,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Import Graph Data",
|
||||
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;
|
||||
case "Tables":
|
||||
@@ -648,14 +592,18 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
{
|
||||
link: "https://aka.ms/tabledotnet",
|
||||
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",
|
||||
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;
|
||||
}
|
||||
@@ -686,24 +634,24 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
private getNextStepItems(): JSX.Element {
|
||||
const items: { link: string; title: string; description: string }[] = [
|
||||
{
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2208312",
|
||||
title: "Data Modeling",
|
||||
description: "",
|
||||
link: "",
|
||||
title: "Performance tuning",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
link: " https://go.microsoft.com/fwlink/?linkid=2206941 ",
|
||||
title: "How to choose a Distribution Column",
|
||||
description: "",
|
||||
link: "",
|
||||
title: "Join Citus community",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2207425",
|
||||
title: "Build Apps with Python/Java/Django",
|
||||
description: "",
|
||||
link: "",
|
||||
title: "Useful diagnostic queries",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack style={{ minWidth: 124, maxWidth: 296 }}>
|
||||
<Stack>
|
||||
{items.map((item, i) => (
|
||||
<Stack key={`nextStep${i}`} style={{ marginBottom: 26 }}>
|
||||
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
|
||||
@@ -722,24 +670,24 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
private getTipsAndLearnMoreItems(): JSX.Element {
|
||||
const items: { link: string; title: string; description: string }[] = [
|
||||
{
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2207226",
|
||||
title: "Performance Tuning",
|
||||
description: "",
|
||||
link: "",
|
||||
title: "Data modeling",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2208037",
|
||||
title: "Useful Diagnostic Queries",
|
||||
description: "",
|
||||
link: "",
|
||||
title: "How to choose a distribution Column",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2205270",
|
||||
title: "Distributed SQL Reference",
|
||||
description: "",
|
||||
link: "",
|
||||
title: "Build apps with Python/ Java/ Django",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack style={{ minWidth: 124, maxWidth: 296 }}>
|
||||
<Stack>
|
||||
{items.map((item, i) => (
|
||||
<Stack key={`tips${i}`} style={{ marginBottom: 26 }}>
|
||||
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
|
||||
|
||||
@@ -516,7 +516,7 @@ export default class QueryBuilderViewModel {
|
||||
};
|
||||
|
||||
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);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component } from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { configContext } from "../../../ConfigContext";
|
||||
import { configContext, Platform } from "../../../ConfigContext";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
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 Explorer from "../../Explorer";
|
||||
import TabsBase from "../TabsBase";
|
||||
import { getMongoShellOrigin } from "./getMongoShellOrigin";
|
||||
import { getMongoShellUrl } from "./getMongoShellUrl";
|
||||
|
||||
//eslint-disable-next-line
|
||||
class MessageType {
|
||||
@@ -49,6 +47,7 @@ export default class MongoShellTabComponent extends Component<
|
||||
IMongoShellTabComponentProps,
|
||||
IMongoShellTabComponentStates
|
||||
> {
|
||||
private _runtimeEndpoint: string;
|
||||
private _logTraces: Map<string, number>;
|
||||
|
||||
constructor(props: IMongoShellTabComponentProps) {
|
||||
@@ -56,7 +55,7 @@ export default class MongoShellTabComponent extends Component<
|
||||
this._logTraces = new Map();
|
||||
|
||||
this.state = {
|
||||
url: getMongoShellUrl(),
|
||||
url: this.getURL(),
|
||||
};
|
||||
|
||||
props.onMongoShellTabAccessor({
|
||||
@@ -66,6 +65,22 @@ export default class MongoShellTabComponent extends Component<
|
||||
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
|
||||
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 apiEndpoint = configContext.BACKEND_ENDPOINT;
|
||||
const encryptedAuthToken: string = userContext.accessToken;
|
||||
const targetOrigin = getMongoShellOrigin();
|
||||
|
||||
shellIframe.contentWindow.postMessage(
|
||||
{
|
||||
@@ -137,7 +151,7 @@ export default class MongoShellTabComponent extends Component<
|
||||
apiEndpoint: apiEndpoint,
|
||||
},
|
||||
},
|
||||
targetOrigin
|
||||
configContext.BACKEND_ENDPOINT
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 'verify-full'
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -70,19 +70,24 @@
|
||||
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button
|
||||
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }"
|
||||
style="border: none; background: none"
|
||||
<div
|
||||
class="addClause"
|
||||
role="button"
|
||||
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }, attr: { title: addNewClauseLine }"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="addClause" data-bind=" ">
|
||||
<div class="addClause-heading">
|
||||
<span class="clause-table addClause-title">
|
||||
<img class="addclauseProperty-Img" style="margin-bottom: 5px" src="/Add-property.svg" />
|
||||
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="addClause-heading">
|
||||
<span class="clause-table addClause-title">
|
||||
<img
|
||||
class="addclauseProperty-Img"
|
||||
style="margin-bottom: 5px"
|
||||
src="/Add-property.svg"
|
||||
alt="Add new clause"
|
||||
/>
|
||||
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tables Query Tab Query Helper - End-->
|
||||
@@ -163,20 +168,22 @@
|
||||
<script type="text/html" id="queryClause-template">
|
||||
<tr class="clause-table-row">
|
||||
<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}"
|
||||
>
|
||||
<span class="entity-Add-Cancel" role="button">
|
||||
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
|
||||
</span>
|
||||
<span
|
||||
class="entity-Add-Cancel"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}"
|
||||
>
|
||||
<span class="entity-Add-Cancel" role="button">
|
||||
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
|
||||
</span>
|
||||
</button>
|
||||
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
|
||||
</span>
|
||||
</td>
|
||||
<td class="clause-table-cell group-control-column">
|
||||
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" />
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||
import { configContext } from "ConfigContext";
|
||||
import { NotebookWorkspaceConnectionInfo, PostgresFirewallRule } from "Contracts/DataModels";
|
||||
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { userContext } from "UserContext";
|
||||
import { armRequest } from "Utils/arm/request";
|
||||
|
||||
interface QuickstartTabProps {
|
||||
explorer: Explorer;
|
||||
@@ -17,42 +13,14 @@ interface QuickstartTabProps {
|
||||
|
||||
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
||||
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(() => {
|
||||
explorer.allocateContainer();
|
||||
}, []);
|
||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
||||
authToken: notebookServerInfo.authToken,
|
||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/mongo`,
|
||||
forwardingId: notebookServerInfo.forwardingId,
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack style={{ width: "100%" }} horizontal>
|
||||
@@ -60,24 +28,15 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
|
||||
<QuickstartGuide />
|
||||
</Stack>
|
||||
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
||||
{!isAllPublicIPAddressEnabled && <QuickstartFirewallNotification />}
|
||||
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
||||
{notebookServerInfo?.notebookServerEndpoint && (
|
||||
<NotebookTerminalComponent
|
||||
notebookServerInfo={getNotebookServerInfo()}
|
||||
databaseAccount={userContext.databaseAccount}
|
||||
tabId="QuickstartPSQLShell"
|
||||
tabId="EmbbedTerminal"
|
||||
/>
|
||||
)}
|
||||
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
||||
<Stack style={{ margin: "auto 0" }}>
|
||||
<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>
|
||||
{!notebookServerInfo?.notebookServerEndpoint && (
|
||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
|
||||
import { sendMessage } from "Common/MessageHandler";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { CollectionTabKind } from "Contracts/ViewModels";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
||||
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
||||
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
||||
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import ko from "knockout";
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { userContext } from "UserContext";
|
||||
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
||||
import errorIcon from "../../../images/close-black.svg";
|
||||
import { useObservable } from "../../hooks/useObservable";
|
||||
@@ -24,23 +19,10 @@ interface TabsProps {
|
||||
}
|
||||
|
||||
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
||||
const { openedTabs, openedReactTabs, activeTab, activeReactTab, networkSettingsWarning } = useTabs();
|
||||
const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs();
|
||||
|
||||
return (
|
||||
<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 className="nav-tabs-margin">
|
||||
<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"}
|
||||
style={active ? { fontWeight: "bolder" } : {}}
|
||||
title={useObservable(tab?.tabPath || ko.observable(""))}
|
||||
aria-selected={active}
|
||||
aria-expanded={active}
|
||||
@@ -208,7 +189,7 @@ const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
|
||||
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
|
||||
switch (activeReactTab) {
|
||||
case ReactTabKind.Connect:
|
||||
return userContext.apiType === "Postgres" ? <PostgresConnectTab /> : <ConnectTab />;
|
||||
return <ConnectTab />;
|
||||
case ReactTabKind.Home:
|
||||
return <SplashScreen explorer={explorer} />;
|
||||
case ReactTabKind.Quickstart:
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||
import { configContext } from "ConfigContext";
|
||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||
import * as ko from "knockout";
|
||||
import * as React from "react";
|
||||
import { armRequest } from "Utils/arm/request";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -29,15 +26,10 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
||||
constructor(
|
||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
||||
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
||||
private getTabId: () => string,
|
||||
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>
|
||||
private getTabId: () => string
|
||||
) {}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
if (!this.isAllPublicIPAddressesEnabled()) {
|
||||
return <QuickstartFirewallNotification />;
|
||||
}
|
||||
|
||||
return this.parameters() ? (
|
||||
<NotebookTerminalComponent
|
||||
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> ';
|
||||
private container: Explorer;
|
||||
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
|
||||
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
|
||||
|
||||
constructor(options: TerminalTabOptions) {
|
||||
super(options);
|
||||
this.container = options.container;
|
||||
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||
() => this.getNotebookServerInfo(options),
|
||||
() => userContext?.databaseAccount,
|
||||
() => this.tabId,
|
||||
this.isAllPublicIPAddressesEnabled
|
||||
() => this.tabId
|
||||
);
|
||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||
if (
|
||||
this.isTemplateReady() &&
|
||||
useNotebook.getState().isNotebookEnabled &&
|
||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
|
||||
this.isAllPublicIPAddressesEnabled()
|
||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
||||
this.checkPostgresFirewallRules();
|
||||
}
|
||||
}
|
||||
|
||||
public getContainer(): Explorer {
|
||||
@@ -111,10 +95,6 @@ export default class TerminalTab extends TabsBase {
|
||||
endpointSuffix = "cassandra";
|
||||
break;
|
||||
|
||||
case ViewModels.TerminalKind.Postgres:
|
||||
endpointSuffix = "postgresql";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Terminal kind: ${options.kind} not supported`);
|
||||
}
|
||||
@@ -126,25 +106,4 @@ export default class TerminalTab extends TabsBase {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,6 +1160,23 @@ export default class Collection implements ViewModels.Collection {
|
||||
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 {
|
||||
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import * as _ from "underscore";
|
||||
import { AuthType } from "../../AuthType";
|
||||
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 { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
@@ -13,7 +13,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
||||
import * as StorageUtility from "../../Shared/StorageUtility";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -39,7 +38,6 @@ export default class Database implements ViewModels.Database {
|
||||
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
||||
public junoClient: JunoClient;
|
||||
public isSampleDB: boolean;
|
||||
public collectionsContinuationToken?: string;
|
||||
private isOfferRead: boolean;
|
||||
|
||||
constructor(container: Explorer, data: DataModels.Database) {
|
||||
@@ -142,11 +140,7 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
await this.loadOffer();
|
||||
|
||||
if (this.collections()?.length === 0) {
|
||||
await this.loadCollections(true);
|
||||
}
|
||||
|
||||
await this.loadCollections();
|
||||
this.isDatabaseExpanded(true);
|
||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
||||
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[] = [];
|
||||
let collections: DataModels.Collection[] = [];
|
||||
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());
|
||||
}
|
||||
|
||||
const collections: DataModels.Collection[] = await readCollections(this.id());
|
||||
// TODO Remove
|
||||
// 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) {
|
||||
@@ -227,9 +199,7 @@ export default class Database implements ViewModels.Database {
|
||||
|
||||
//merge collections
|
||||
this.addCollectionsToList(collectionVMs);
|
||||
if (!containerPaginationEnabled || restart) {
|
||||
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
||||
}
|
||||
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
||||
|
||||
useDatabases.getState().updateDatabase(this);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { getItemName } from "Utils/APITypeUtils";
|
||||
import shallow from "zustand/shallow";
|
||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.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))
|
||||
);
|
||||
|
||||
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[]) => {
|
||||
collections.forEach((collection: ViewModels.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 children: TreeNode[] = [];
|
||||
children.push({
|
||||
label: getItemName(),
|
||||
label: collection.getLabel(),
|
||||
id: collection.isSampleCollection ? "sampleItems" : "",
|
||||
onClick: () => {
|
||||
collection.openTab();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||
import * as ko from "knockout";
|
||||
import * as React from "react";
|
||||
import { getItemName } from "Utils/APITypeUtils";
|
||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||
import DeleteIcon from "../../../images/delete.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 {
|
||||
const children: TreeNode[] = [];
|
||||
children.push({
|
||||
label: getItemName(),
|
||||
label: collection.getLabel(),
|
||||
onClick: () => {
|
||||
collection.openTab();
|
||||
// push to most recent
|
||||
|
||||
@@ -80,7 +80,6 @@ const App: React.FunctionComponent = () => {
|
||||
return (
|
||||
<div className="flexContainer">
|
||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||
<div id="freeTierTeachingBubble"> </div>
|
||||
{/* Main Command Bar - Start */}
|
||||
<CommandBar container={explorer} />
|
||||
{/* Collections Tree and Tabs - Begin */}
|
||||
|
||||
@@ -32,7 +32,6 @@ import { userContext } from "../UserContext";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
|
||||
export class PhoenixClient {
|
||||
private armResourceId: string;
|
||||
private containerHealthHandler: NodeJS.Timeout;
|
||||
private retryOptions: promiseRetry.Options = {
|
||||
retries: Notebook.retryAttempts,
|
||||
@@ -40,16 +39,8 @@ export class PhoenixClient {
|
||||
minTimeout: Notebook.retryAttemptDelayMs,
|
||||
};
|
||||
|
||||
constructor(armResourceId: string) {
|
||||
this.armResourceId = armResourceId;
|
||||
}
|
||||
|
||||
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
||||
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
|
||||
retries: 4,
|
||||
maxTimeout: 20000,
|
||||
minTimeout: 20000,
|
||||
});
|
||||
return this.executeContainerAssignmentOperation(provisionData, "allocate");
|
||||
}
|
||||
|
||||
public async resetContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
||||
@@ -84,12 +75,9 @@ export class PhoenixClient {
|
||||
}
|
||||
const phoenixError = responseJson as IPhoenixError;
|
||||
if (response.status === HttpStatusCodes.Forbidden) {
|
||||
if (phoenixError.message === "Sequence contains no elements") {
|
||||
throw Error("Phoenix container allocation failed, please try again later.");
|
||||
}
|
||||
throw new AbortError(this.ConvertToForbiddenErrorString(phoenixError));
|
||||
throw new Error(this.ConvertToForbiddenErrorString(phoenixError));
|
||||
}
|
||||
throw new AbortError(phoenixError.message);
|
||||
throw new Error(phoenixError.message);
|
||||
} catch (error) {
|
||||
error.status = response?.status;
|
||||
throw error;
|
||||
@@ -226,21 +214,22 @@ export class PhoenixClient {
|
||||
}
|
||||
}
|
||||
|
||||
private getPhoenixControlPlanePathPrefix(): string {
|
||||
if (!this.armResourceId) {
|
||||
throw new Error("The Phoenix client was not initialized properly: missing ARM resourcce id");
|
||||
}
|
||||
|
||||
const toolsEndpoint =
|
||||
public static getPhoenixEndpoint(): string {
|
||||
const phoenixEndpoint =
|
||||
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||
|
||||
if (!validateEndpoint(toolsEndpoint, allowedJunoOrigins)) {
|
||||
const error = `${toolsEndpoint} not allowed as tools endpoint`;
|
||||
if (!validateEndpoint(phoenixEndpoint, allowedJunoOrigins)) {
|
||||
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
||||
console.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 {
|
||||
|
||||
31
src/Platform/Hosted/Authorization.ts
Normal file
31
src/Platform/Hosted/Authorization.ts
Normal 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}`;
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,7 @@ export type Features = {
|
||||
readonly mongoProxyEndpoint?: string;
|
||||
readonly mongoProxyAPIs?: string;
|
||||
readonly enableThroughputCap: boolean;
|
||||
readonly enableHierarchicalKeys: boolean;
|
||||
readonly enableLegacyMongoShellV1: boolean;
|
||||
readonly enableLegacyMongoShellV1Debug: boolean;
|
||||
readonly enableLegacyMongoShellV2: boolean;
|
||||
readonly enableLegacyMongoShellV2Debug: boolean;
|
||||
readonly loadLegacyMongoShellFromBE: boolean;
|
||||
readonly enablePGQuickstart: boolean;
|
||||
|
||||
// can be set via both flight and feature flag
|
||||
autoscaleDefault: boolean;
|
||||
@@ -96,12 +91,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
||||
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
||||
notebooksDownBanner: "true" === get("notebooksDownBanner"),
|
||||
enableThroughputCap: "true" === get("enablethroughputcap"),
|
||||
enableHierarchicalKeys: "true" === get("enablehierarchicalkeys"),
|
||||
enableLegacyMongoShellV1: "true" === get("enablelegacymongoshellv1"),
|
||||
enableLegacyMongoShellV1Debug: "true" === get("enablelegacymongoshellv1debug"),
|
||||
enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
|
||||
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
|
||||
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
|
||||
enablePGQuickstart: "true" === get("enablepgquickstart"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -40,23 +40,23 @@ export enum SelfServeType {
|
||||
*/
|
||||
export enum BladeType {
|
||||
/**
|
||||
* Keys blade of a Azure Cosmos DB for NoSQL account.
|
||||
* Keys blade of a SQL API account.
|
||||
*/
|
||||
SqlKeys = "keys",
|
||||
/**
|
||||
* Keys blade of a Azure Cosmos DB for MongoDB account.
|
||||
* Keys blade of a Mongo API account.
|
||||
*/
|
||||
MongoKeys = "mongoDbKeys",
|
||||
/**
|
||||
* Keys blade of a Azure Cosmos DB for Apache Cassandra account.
|
||||
* Keys blade of a Cassandra API account.
|
||||
*/
|
||||
CassandraKeys = "cassandraDbKeys",
|
||||
/**
|
||||
* Keys blade of a Azure Cosmos DB for Apache Gremlin account.
|
||||
* Keys blade of a Gremlin API account.
|
||||
*/
|
||||
GremlinKeys = "keys",
|
||||
/**
|
||||
* Keys blade of a Azure Cosmos DB for Table account.
|
||||
* Keys blade of a Table API account.
|
||||
*/
|
||||
TableKeys = "tableKeys",
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as SessionStorageUtility from "./SessionStorageUtility";
|
||||
export { LocalStorageUtility, SessionStorageUtility };
|
||||
export enum StorageKey {
|
||||
ActualItemPerPage,
|
||||
ContainerPaginationEnabled,
|
||||
CustomItemPerPage,
|
||||
DatabaseAccountId,
|
||||
EncryptedKeyToken,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* JupyterLab applications based on jupyterLab components
|
||||
*/
|
||||
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 { Panel, Widget } from "@phosphor/widgets";
|
||||
import { userContext } from "UserContext";
|
||||
@@ -24,10 +24,6 @@ export class JupyterLabAppFactory {
|
||||
this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh");
|
||||
}
|
||||
|
||||
private isPostgresShellStarted(content: string | undefined) {
|
||||
this.isShellStarted = content?.includes("citus=>");
|
||||
}
|
||||
|
||||
constructor(closeTab: () => void) {
|
||||
this.onShellExited = closeTab;
|
||||
this.isShellStarted = false;
|
||||
@@ -40,13 +36,10 @@ export class JupyterLabAppFactory {
|
||||
case "Cassandra":
|
||||
this.checkShellStarted = this.isCassandraShellStarted;
|
||||
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({
|
||||
serverSettings: serverSettings,
|
||||
});
|
||||
@@ -68,7 +61,7 @@ export class JupyterLabAppFactory {
|
||||
|
||||
if (!term) {
|
||||
console.error("Failed starting terminal");
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
term.title.closable = false;
|
||||
@@ -81,9 +74,6 @@ export class JupyterLabAppFactory {
|
||||
// Attach the widget to the dom.
|
||||
Widget.attach(panel, document.body);
|
||||
|
||||
// Switch focus to the terminal
|
||||
term.activate();
|
||||
|
||||
// Handle resize events.
|
||||
window.addEventListener("resize", () => {
|
||||
panel.update();
|
||||
@@ -93,7 +83,5 @@ export class JupyterLabAppFactory {
|
||||
window.addEventListener("unload", () => {
|
||||
panel.dispose();
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ServerConnection } from "@jupyterlab/services";
|
||||
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal";
|
||||
import "@jupyterlab/terminal/style/index.css";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import postRobot from "post-robot";
|
||||
@@ -42,7 +41,7 @@ const createServerSettings = (props: TerminalProps): ServerConnection.ISettings
|
||||
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)
|
||||
updateUserContext({
|
||||
subscriptionId: props.subscriptionId,
|
||||
@@ -56,12 +55,10 @@ const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection |
|
||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
|
||||
|
||||
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);
|
||||
return session;
|
||||
} catch (error) {
|
||||
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,7 +70,6 @@ const closeTab = (tabId: string): void => {
|
||||
};
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
let session: ITerminalConnection | undefined;
|
||||
postRobot.on(
|
||||
"props",
|
||||
{
|
||||
@@ -84,22 +80,7 @@ const main = async (): Promise<void> => {
|
||||
// Typescript definition for event is wrong. So read props by casting to <any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props = (event as any).data as TerminalProps;
|
||||
session = 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);
|
||||
}
|
||||
await initTerminal(props);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,20 +26,6 @@ export interface CollectionCreationDefaults {
|
||||
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 {
|
||||
readonly authType?: AuthType;
|
||||
readonly masterKey?: string;
|
||||
@@ -66,8 +52,6 @@ interface UserContext {
|
||||
collectionId: string;
|
||||
partitionKey?: string;
|
||||
};
|
||||
readonly postgresConnectionStrParams?: PostgresConnectionStrParams;
|
||||
readonly isReplica?: boolean;
|
||||
collectionCreationDefaults: CollectionCreationDefaults;
|
||||
}
|
||||
|
||||
@@ -102,7 +86,7 @@ function isAccountNewerThanThresholdInMs(createdAt: string, threshold: number) {
|
||||
|
||||
function updateUserContext(newContext: Partial<UserContext>): void {
|
||||
if (newContext.databaseAccount) {
|
||||
newContext.apiType = apiType(newContext.databaseAccount);
|
||||
newContext.apiType = "Postgres";
|
||||
|
||||
const isNewAccount = isAccountNewerThanThresholdInMs(
|
||||
newContext.databaseAccount?.systemData?.createdAt || "",
|
||||
@@ -110,51 +94,43 @@ function updateUserContext(newContext: Partial<UserContext>): void {
|
||||
);
|
||||
|
||||
if (!localStorage.getItem(newContext.databaseAccount.id)) {
|
||||
if (newContext.isTryCosmosDBSubscription || isNewAccount) {
|
||||
if (newContext.apiType === "Postgres" && !newContext.isReplica) {
|
||||
usePostgres.getState().setShowResetPasswordBubble(true);
|
||||
usePostgres.getState().setShowPostgreTeachingBubble(true);
|
||||
} else {
|
||||
useCarousel.getState().setShouldOpen(true);
|
||||
localStorage.setItem(newContext.databaseAccount.id, "true");
|
||||
traceOpen(Action.OpenCarousel);
|
||||
}
|
||||
} else if (newContext.apiType === "Postgres") {
|
||||
if (newContext.apiType === "Postgres") {
|
||||
usePostgres.getState().setShowPostgreTeachingBubble(true);
|
||||
localStorage.setItem(newContext.databaseAccount.id, "true");
|
||||
} else if (userContext.isTryCosmosDBSubscription || isNewAccount) {
|
||||
useCarousel.getState().setShouldOpen(true);
|
||||
localStorage.setItem(newContext.databaseAccount.id, "true");
|
||||
traceOpen(Action.OpenCarousel);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(userContext, newContext);
|
||||
}
|
||||
|
||||
function apiType(account: DatabaseAccount | undefined): ApiType {
|
||||
if (!account) {
|
||||
return "SQL";
|
||||
}
|
||||
// function apiType(account: DatabaseAccount | undefined): ApiType {
|
||||
// if (!account) {
|
||||
// return "SQL";
|
||||
// }
|
||||
|
||||
const capabilities = account.properties?.capabilities;
|
||||
if (capabilities) {
|
||||
if (capabilities.find((c) => c.name === "EnableCassandra")) {
|
||||
return "Cassandra";
|
||||
}
|
||||
if (capabilities.find((c) => c.name === "EnableGremlin")) {
|
||||
return "Gremlin";
|
||||
}
|
||||
if (capabilities.find((c) => c.name === "EnableMongo")) {
|
||||
return "Mongo";
|
||||
}
|
||||
if (capabilities.find((c) => c.name === "EnableTable")) {
|
||||
return "Tables";
|
||||
}
|
||||
}
|
||||
if (account.kind === "MongoDB" || account.kind === "Parse") {
|
||||
return "Mongo";
|
||||
}
|
||||
if (account.kind === "Postgres") {
|
||||
return "Postgres";
|
||||
}
|
||||
return "SQL";
|
||||
}
|
||||
// const capabilities = account.properties?.capabilities;
|
||||
// if (capabilities) {
|
||||
// if (capabilities.find((c) => c.name === "EnableCassandra")) {
|
||||
// return "Cassandra";
|
||||
// }
|
||||
// if (capabilities.find((c) => c.name === "EnableGremlin")) {
|
||||
// return "Gremlin";
|
||||
// }
|
||||
// if (capabilities.find((c) => c.name === "EnableMongo")) {
|
||||
// return "Mongo";
|
||||
// }
|
||||
// if (capabilities.find((c) => c.name === "EnableTable")) {
|
||||
// return "Tables";
|
||||
// }
|
||||
// }
|
||||
// if (account.kind === "MongoDB" || account.kind === "Parse") {
|
||||
// return "Mongo";
|
||||
// }
|
||||
// return "SQL";
|
||||
// }
|
||||
|
||||
export { userContext, updateUserContext };
|
||||
|
||||
@@ -57,35 +57,3 @@ export const getUploadName = (): string => {
|
||||
return "Items";
|
||||
}
|
||||
};
|
||||
|
||||
export const getApiShortDisplayName = (): string => {
|
||||
switch (userContext.apiType) {
|
||||
case "Cassandra":
|
||||
return "Apache Cassandra API";
|
||||
case "Gremlin":
|
||||
return "Apache Gremlin API";
|
||||
case "Mongo":
|
||||
return "MongoDB API";
|
||||
case "Postgres":
|
||||
return "PostgreSQL API";
|
||||
case "SQL":
|
||||
return "NoSQL API";
|
||||
case "Tables":
|
||||
return "Table API";
|
||||
}
|
||||
};
|
||||
|
||||
export const getItemName = (): string => {
|
||||
switch (userContext.apiType) {
|
||||
case "Tables":
|
||||
return "Entities";
|
||||
case "Cassandra":
|
||||
return "Rows";
|
||||
case "Gremlin":
|
||||
return "Graph";
|
||||
case "Mongo":
|
||||
return "Documents";
|
||||
default:
|
||||
return "Items";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export function getMsalInstance() {
|
||||
cacheLocation: "localStorage",
|
||||
},
|
||||
auth: {
|
||||
authority: `${configContext.AAD_ENDPOINT}organizations`,
|
||||
authority: `${configContext.AAD_ENDPOINT}common`,
|
||||
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,23 +21,6 @@ function isValidOrigin(allowedOrigins: ReadonlyArray<string>, event: MessageEven
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shouldProcessMessage(event: MessageEvent): boolean {
|
||||
if (typeof event.data !== "object") {
|
||||
return false;
|
||||
}
|
||||
if (event.data["signature"] !== "pcIframe") {
|
||||
return false;
|
||||
}
|
||||
if (!("data" in event.data)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof event.data["data"] !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isReadyMessage(event: MessageEvent): boolean {
|
||||
if (!event?.data?.kind && !event?.data?.data) {
|
||||
return false;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { userContext } from "UserContext";
|
||||
|
||||
const PortalIPs: { [key: string]: string[] } = {
|
||||
prod1: ["104.42.195.92", "40.76.54.131"],
|
||||
prod2: ["104.42.196.69"],
|
||||
mooncake: ["139.217.8.252"],
|
||||
blackforest: ["51.4.229.218"],
|
||||
fairfax: ["52.244.48.71"],
|
||||
ussec: ["29.26.26.67", "29.26.26.66"],
|
||||
usnat: ["7.28.202.68"],
|
||||
};
|
||||
|
||||
export const getNetworkSettingsWarningMessage = (): string => {
|
||||
const accountProperties = userContext.databaseAccount?.properties;
|
||||
|
||||
if (!accountProperties) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// public network access is disabled
|
||||
if (accountProperties.publicNetworkAccess !== "Enabled") {
|
||||
return "The Network settings for this account are preventing access from Data Explorer. Please enable public access to proceed.";
|
||||
}
|
||||
|
||||
const ipRules = accountProperties.ipRules;
|
||||
// public network access is set to "All networks"
|
||||
if (ipRules.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (userContext.apiType === "Cassandra" || userContext.apiType === "Mongo") {
|
||||
const portalIPs = PortalIPs[userContext.portalEnv];
|
||||
let numberOfMatches = 0;
|
||||
ipRules.forEach((ipRule) => {
|
||||
if (portalIPs.indexOf(ipRule.ipAddressOrRange) !== -1) {
|
||||
numberOfMatches++;
|
||||
}
|
||||
});
|
||||
|
||||
if (numberOfMatches !== portalIPs.length) {
|
||||
return "The Network settings for this account are preventing access from Data Explorer. Please allow access from Azure Portal to proceed.";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
18
src/hooks/useFullScreenURLs.tsx
Normal file
18
src/hooks/useFullScreenURLs.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { GenerateTokenResponse } from "../Contracts/DataModels";
|
||||
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
|
||||
|
||||
export function useFullScreenURLs(): GenerateTokenResponse | undefined {
|
||||
const [state, setState] = useState<GenerateTokenResponse>();
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([AuthHeadersUtil.generateEncryptedToken(), AuthHeadersUtil.generateEncryptedToken(true)]).then(
|
||||
([readWriteResponse, readOnlyResponse]) =>
|
||||
setState({
|
||||
readWrite: readWriteResponse.readWrite,
|
||||
read: readOnlyResponse.read,
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
return state;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||
import { applyExplorerBindings } from "../applyExplorerBindings";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { AccountKind, Flights } from "../Common/Constants";
|
||||
@@ -11,6 +9,7 @@ import { configContext, Platform, updateConfigContext } from "../ConfigContext";
|
||||
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
||||
import { useDatabases } from "../Explorer/useDatabases";
|
||||
import {
|
||||
@@ -29,11 +28,11 @@ import {
|
||||
} from "../Platform/Hosted/HostedUtils";
|
||||
import { CollectionCreation } from "../Shared/Constants";
|
||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
|
||||
import { PortalEnv, updateUserContext, userContext } from "../UserContext";
|
||||
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
|
||||
import { getMsalInstance } from "../Utils/AuthorizationUtils";
|
||||
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
|
||||
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
||||
|
||||
// This hook will create a new instance of Explorer.ts and bind it to the DOM
|
||||
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
|
||||
@@ -101,11 +100,7 @@ async function configureHosted(): Promise<Explorer> {
|
||||
}
|
||||
|
||||
if (event.data?.type === MessageTypes.CloseTab) {
|
||||
if (event.data?.data?.tabId === "QuickstartPSQLShell") {
|
||||
useTabs.getState().closeReactTab(ReactTabKind.Quickstart);
|
||||
} else {
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
|
||||
}
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
|
||||
}
|
||||
},
|
||||
false
|
||||
@@ -239,7 +234,6 @@ async function configurePortal(): Promise<Explorer> {
|
||||
updateUserContext({
|
||||
authType: AuthType.AAD,
|
||||
});
|
||||
let explorer: Explorer;
|
||||
return new Promise((resolve) => {
|
||||
// In development mode, try to load the iframe message from session storage.
|
||||
// This allows webpack hot reload to function properly in the portal
|
||||
@@ -252,7 +246,7 @@ async function configurePortal(): Promise<Explorer> {
|
||||
);
|
||||
console.dir(message);
|
||||
updateContextsFromPortalMessage(message);
|
||||
explorer = new Explorer();
|
||||
const explorer = new Explorer();
|
||||
// In development mode, save the iframe message from the portal in session storage.
|
||||
// This allows webpack hot reload to funciton properly
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
@@ -288,7 +282,7 @@ async function configurePortal(): Promise<Explorer> {
|
||||
}
|
||||
|
||||
updateContextsFromPortalMessage(inputs);
|
||||
explorer = new Explorer();
|
||||
const explorer = new Explorer();
|
||||
resolve(explorer);
|
||||
if (openAction) {
|
||||
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||
@@ -296,13 +290,7 @@ async function configurePortal(): Promise<Explorer> {
|
||||
} else if (shouldForwardMessage(message, event.origin)) {
|
||||
sendMessage(message);
|
||||
} else if (event.data?.type === MessageTypes.CloseTab) {
|
||||
if (event.data?.data?.tabId === "QuickstartPSQLShell") {
|
||||
useTabs.getState().closeReactTab(ReactTabKind.Quickstart);
|
||||
} else {
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
|
||||
}
|
||||
} else if (message?.type === MessageTypes.RefreshResources) {
|
||||
explorer.onRefreshResourcesClick();
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
|
||||
}
|
||||
},
|
||||
false
|
||||
@@ -317,6 +305,23 @@ function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
||||
return messageOrigin === window.document.location.origin && message.type === MessageTypes.TelemetryInfo;
|
||||
}
|
||||
|
||||
function shouldProcessMessage(event: MessageEvent): boolean {
|
||||
if (typeof event.data !== "object") {
|
||||
return false;
|
||||
}
|
||||
if (event.data["signature"] !== "pcIframe") {
|
||||
return false;
|
||||
}
|
||||
if (!("data" in event.data)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof event.data["data"] !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
||||
if (
|
||||
configContext.BACKEND_ENDPOINT &&
|
||||
@@ -349,32 +354,9 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
||||
collectionCreationDefaults: inputs.defaultCollectionThroughput,
|
||||
isTryCosmosDBSubscription: inputs.isTryCosmosDBSubscription,
|
||||
});
|
||||
|
||||
if (inputs.isPostgresAccount) {
|
||||
updateUserContext({ apiType: "Postgres", isReplica: !!inputs.isReplica });
|
||||
|
||||
if (inputs.connectionStringParams) {
|
||||
// TODO: Remove after the nodes param has been updated to be a flat array in the OSS extension
|
||||
let flattenedNodesArray: Node[] = [];
|
||||
inputs.connectionStringParams.nodes?.forEach((node: Node | Node[]) => {
|
||||
if (Array.isArray(node)) {
|
||||
flattenedNodesArray = [...flattenedNodesArray, ...node];
|
||||
} else {
|
||||
flattenedNodesArray.push(node);
|
||||
}
|
||||
});
|
||||
inputs.connectionStringParams.nodes = flattenedNodesArray;
|
||||
updateUserContext({ postgresConnectionStrParams: inputs.connectionStringParams });
|
||||
}
|
||||
}
|
||||
|
||||
const warningMessage = getNetworkSettingsWarningMessage();
|
||||
useTabs.getState().setNetworkSettingsWarning(warningMessage);
|
||||
|
||||
if (inputs.features) {
|
||||
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));
|
||||
}
|
||||
|
||||
if (inputs.flights) {
|
||||
if (inputs.flights.indexOf(Flights.AutoscaleTest) !== -1) {
|
||||
userContext.features.autoscaleDefault;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user